summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--chimere/actions.py52
-rw-r--r--chimere/admin.py111
-rw-r--r--chimere/feeds.py14
-rw-r--r--chimere/fixtures/initial_data.json32
-rw-r--r--chimere/fixtures/layers-default-openlayers.json34
-rw-r--r--chimere/forms.py39
-rw-r--r--chimere/locale/fr/LC_MESSAGES/django.po835
-rw-r--r--chimere/migrations/0009_auto__del_arealayers__del_area__add_map__add_mapgroups__add_mapusers__.py503
-rw-r--r--chimere/migrations/0010_auto__add_field_marker_keywords__add_field_route_keywords.py (renamed from chimere/migrations/0009_auto__add_field_marker_keywords__add_field_route_keywords.py)126
-rw-r--r--chimere/models.py316
-rwxr-xr-xchimere/scripts/upgrade.py315
-rw-r--r--chimere/settings.sample.py72
-rw-r--r--chimere/src/default-bg.xcfbin0 -> 3214457 bytes
-rw-r--r--chimere/src/default_bg.xcfbin0 -> 3231304 bytes
-rwxr-xr-xchimere/src/marker-cluster-large.xcfbin0 -> 4179 bytes
-rwxr-xr-xchimere/src/marker-cluster-medium.xcfbin0 -> 2883 bytes
-rwxr-xr-xchimere/src/marker-cluster-small.xcfbin0 -> 2423 bytes
-rwxr-xr-xchimere/src/marker-cluster-xlarge.xcfbin0 -> 3125 bytes
-rwxr-xr-xchimere/src/marker-cluster-xsmall.xcfbin0 -> 2407 bytes
-rwxr-xr-xchimere/src/marker-cluster-xxlarge.xcfbin0 -> 4257 bytes
-rwxr-xr-x[-rw-r--r--]chimere/src/share-icon.xcf (renamed from chimere/static/chimere/img/share-icon.xcf)bin5431 -> 5431 bytes
-rw-r--r--chimere/static/chimere/css/styles.css209
-rw-r--r--chimere/static/chimere/img/ajax-loader-small.gifbin0 -> 1553 bytes
-rw-r--r--chimere/static/chimere/img/default-bg.jpgbin0 -> 82181 bytes
-rw-r--r--chimere/static/chimere/img/marker-cluster-large.pngbin0 -> 1115 bytes
-rw-r--r--chimere/static/chimere/img/marker-cluster-medium.pngbin0 -> 861 bytes
-rw-r--r--chimere/static/chimere/img/marker-cluster-small.pngbin0 -> 2067 bytes
-rw-r--r--chimere/static/chimere/img/marker-cluster-xlarge.pngbin0 -> 1182 bytes
-rw-r--r--chimere/static/chimere/img/marker-cluster-xsmall.pngbin0 -> 693 bytes
-rw-r--r--chimere/static/chimere/img/marker-cluster-xxlarge.pngbin0 -> 1364 bytes
-rw-r--r--chimere/static/chimere/js/base.js14
-rw-r--r--chimere/static/chimere/js/jquery.chimere-leaflet.js707
-rw-r--r--chimere/static/chimere/js/jquery.chimere-ol.js (renamed from chimere/static/chimere/js/jquery.chimere.js)78
-rw-r--r--chimere/static/chimere/js/nominatim-widget.js7
-rw-r--r--chimere/static/leaflet-markercluster/CHANGELOG.md49
-rw-r--r--chimere/static/leaflet-markercluster/MIT-LICENCE.txt20
-rw-r--r--chimere/static/leaflet-markercluster/MarkerCluster.Default.css38
-rw-r--r--chimere/static/leaflet-markercluster/MarkerCluster.Default.ie.css22
-rw-r--r--chimere/static/leaflet-markercluster/MarkerCluster.css6
-rw-r--r--chimere/static/leaflet-markercluster/README.md125
-rw-r--r--chimere/static/leaflet-markercluster/leaflet.markercluster.js6
-rw-r--r--chimere/static/leaflet/LICENSE23
-rw-r--r--chimere/static/leaflet/README.md28
-rw-r--r--chimere/static/leaflet/images/layers.pngbin0 -> 973 bytes
-rw-r--r--chimere/static/leaflet/images/marker-icon.pngbin0 -> 1747 bytes
-rw-r--r--chimere/static/leaflet/images/marker-icon@2x.pngbin0 -> 4033 bytes
-rw-r--r--chimere/static/leaflet/images/marker-shadow.pngbin0 -> 797 bytes
-rw-r--r--chimere/static/leaflet/leaflet-src.js8338
-rw-r--r--chimere/static/leaflet/leaflet.css457
-rw-r--r--chimere/static/leaflet/leaflet.ie.css57
-rw-r--r--chimere/static/leaflet/leaflet.js8
-rw-r--r--chimere/templates/chimere/base.html4
-rw-r--r--chimere/templates/chimere/blocks/actions.html2
-rw-r--r--chimere/templates/chimere/blocks/areas.html25
-rw-r--r--chimere/templates/chimere/blocks/areas_alternative.html11
-rw-r--r--chimere/templates/chimere/blocks/head_chimere.html16
-rw-r--r--chimere/templates/chimere/blocks/live_coordinates.html6
-rw-r--r--chimere/templates/chimere/blocks/map.html19
-rw-r--r--chimere/templates/chimere/blocks/maps.html29
-rw-r--r--chimere/templates/chimere/blocks/maps_alternative.html11
-rw-r--r--chimere/templates/chimere/blocks/news.html2
-rw-r--r--chimere/templates/chimere/blocks/nominatim_widget.html17
-rw-r--r--chimere/templates/chimere/blocks/submited.html19
-rw-r--r--chimere/templates/chimere/blocks/welcome.html2
-rw-r--r--chimere/templates/chimere/detail.html9
-rw-r--r--chimere/templates/chimere/edit.html66
-rw-r--r--chimere/templates/chimere/edit_route.html33
-rw-r--r--chimere/templates/chimere/feeds/rss.html10
-rw-r--r--chimere/templates/chimere/main_map.html21
-rw-r--r--chimere/templates/chimere/no_map.html41
-rw-r--r--chimere/templatetags/chimere_tags.py137
-rw-r--r--chimere/tests.py132
-rw-r--r--chimere/urls.py63
-rw-r--r--chimere/utils.py48
-rw-r--r--chimere/views.py321
-rw-r--r--chimere/widgets.py100
76 files changed, 12393 insertions, 1392 deletions
diff --git a/chimere/actions.py b/chimere/actions.py
index 8ef5338..824b14f 100644
--- a/chimere/actions.py
+++ b/chimere/actions.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
-# Copyright (C) 2008-2010 Étienne Loks <etienne.loks_AT_peacefrogsDOTnet>
+# Copyright (C) 2008-2013 Étienne Loks <etienne.loks_AT_peacefrogsDOTnet>
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as
@@ -25,41 +25,57 @@ from django.contrib.auth import models
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext_lazy as _
-from models import Page
+from chimere.models import Page, Map
class Action:
- def __init__(self, id, path, label, extra_url_args=[]):
+ def __init__(self, id, path, label, extra_url_args=[],
+ condition=None):
self.id, self.path, self.label = id, path, label
self.extra_url_args, self.url = extra_url_args, None
+ self.condition = condition
- def update_url(self, area_name):
+ def update_url(self, map_name):
self.url = reverse(self.path,
- args=[area_name + '/' if area_name else ''] + self.extra_url_args)
+ args=[map_name + '/' if map_name else ''] + self.extra_url_args)
-default_actions = [(Action('view', 'chimere:index', _('View')), []),
- (Action('contribute', 'chimere:edit', _('Contribute')),
+DEFAULT_ACTIONS = [[Action('view', 'chimere:index', _('View')), []],
+ [Action('contribute', 'chimere:edit', _('Contribute'),
+ condition=lambda user, map_name:bool(
+ Map.getAvailable(user=user, urn=map_name, single=True,
+ propose=True))),
(Action('edit', 'chimere:edit', _('Add a new point of interest')),
Action('edit-route', 'chimere:editroute', _('Add a new route'))),
- ),]
+ ],
+ ]
if settings.CHIMERE_FEEDS:
- default_actions.append((Action('rss', 'chimere:feeds-form',
- _('RSS feeds')), []))
+ DEFAULT_ACTIONS.append([Action('rss', 'chimere:feeds-form',
+ _('RSS feeds')), []])
if settings.EMAIL_HOST:
- default_actions.append((Action('contact', 'chimere:contact',
- _('Contact us')), []),)
+ DEFAULT_ACTIONS.append([Action('contact', 'chimere:contact',
+ _('Contact us')), []],)
-
-def actions(area_name=''):
- acts = default_actions[:]
+def actions(user, map_name='', default_actions=DEFAULT_ACTIONS):
+ acts, idx = [], -1
for act, childs in default_actions:
- act.update_url(area_name)
+ if act.id not in settings.CHIMERE_DEFAULT_ACTIONS:
+ continue
+ idx += 1
+ if act.condition:
+ if not act.condition(user, map_name):
+ continue
+ act.update_url(map_name)
for child_act in childs:
- child_act.update_url(area_name)
+ child_act.update_url(map_name)
+ if "CHIMERE_DEFAULT_ACTION_LABEL" in dir(settings):
+ if len(settings.CHIMERE_DEFAULT_ACTION_LABEL) > idx:
+ act.label = settings.CHIMERE_DEFAULT_ACTION_LABEL[idx]
+ acts.append((act, childs))
for page in Page.objects.filter(available=True).order_by('order'):
act = Action(page.mnemonic, 'chimere:extra_page', page.title,
[page.mnemonic])
- act.update_url(area_name)
+ act.update_url(map_name)
acts.append((act, []))
+
return acts
diff --git a/chimere/admin.py b/chimere/admin.py
index a93f96d..2672306 100644
--- a/chimere/admin.py
+++ b/chimere/admin.py
@@ -25,6 +25,8 @@ import datetime
from django import forms
from django.conf import settings
from django.contrib import admin, messages
+from django.contrib.auth.models import User, Group
+from django.contrib.auth.admin import GroupAdmin
from django.core.exceptions import ObjectDoesNotExist
from django.db.models import Q
from django.http import HttpResponse, HttpResponseRedirect
@@ -36,17 +38,30 @@ try:
except ImportError:
pass
-from chimere.forms import MarkerAdminForm, RouteAdminForm, AreaAdminForm,\
+from chimere.forms import MarkerAdminForm, RouteAdminForm, MapAdminForm,\
NewsAdminForm, CategoryAdminForm, ImporterAdminForm, OSMForm, \
PageAdminForm, PictureFileAdminForm, MultimediaFileAdminForm
from chimere.models import Category, Icon, SubCategory, Marker, \
- PropertyModel, News, Route, Area, ColorTheme, Color, \
- MultimediaFile, PictureFile, Importer, Layer, AreaLayers,\
- PropertyModelChoice, MultimediaExtension, Page,\
- get_areas_for_user, get_users_by_area, ImporterKeyCategories
+ PropertyModel, News, Route, Map, ColorTheme, Color, \
+ MultimediaFile, PictureFile, Importer, Layer, MapLayers,\
+ PropertyModelChoice, MultimediaExtension, Page, MapUsers, MapGroups,\
+ get_maps_for_user, get_users_by_map, ImporterKeyCategories
from chimere.utils import unicode_normalize, ShapefileManager, KMLManager,\
CSVManager
+admin.site.unregister(Group)
+
+class UserInline(admin.StackedInline):
+ model = User.groups.through
+ verbose_name = _(u'User')
+ verbose_name_plural = _(u'User')
+ extra = 1
+
+class GroupAdmin(GroupAdmin):
+ inlines = [ UserInline, ]
+
+admin.site.register(Group, GroupAdmin)
+
def disable(modeladmin, request, queryset):
for item in queryset:
item.status = 'D'
@@ -84,14 +99,18 @@ def export_to_shapefile(modeladmin, request, queryset):
return response
export_to_shapefile.short_description = _(u"Export to Shapefile")
-def export_to_csv(modeladmin, request, queryset):
- u"""
- Export data to CSV
- """
- filename, result = CSVManager.export(queryset)
- response = HttpResponse(result, mimetype='text/csv')
- response['Content-Disposition'] = 'attachment; filename=%s' % filename
- return response
+def _export_to_csv(cols=[]):
+ def func(modeladmin, request, queryset):
+ u"""
+ Export data to CSV
+ """
+ filename, result = CSVManager.export(queryset, cols=cols)
+ response = HttpResponse(result, mimetype='text/csv')
+ response['Content-Disposition'] = 'attachment; filename=%s' % filename
+ return response
+ return func
+
+export_to_csv = _export_to_csv()
export_to_csv.short_description = _(u"Export to CSV")
def managed_modified(modeladmin, request, queryset):
@@ -192,7 +211,7 @@ class MarkerAdmin(admin.ModelAdmin):
form = MarkerAdminForm
fieldsets = ((None, {
'fields': ['point', 'name', 'status', 'categories',
- 'description', 'keywords', 'start_date', 'end_date']
+ 'description', 'weight', 'keywords', 'start_date', 'end_date']
}),
(_(u"Submitter"), {
'classes':('collapse',),
@@ -228,10 +247,10 @@ class MarkerAdmin(admin.ModelAdmin):
def queryset(self, request):
qs = self.model._default_manager.get_query_set()
if not request.user.is_superuser:
- areas = get_areas_for_user(request.user)
+ maps = get_maps_for_user(request.user)
contained = Q()
- for area in areas:
- contained = contained | area.getIncludeMarker()
+ for map in maps:
+ contained = contained | map.getIncludeMarker()
qs = qs.filter(contained)
ordering = self.ordering or ()
if ordering:
@@ -255,7 +274,6 @@ class MarkerAdmin(admin.ModelAdmin):
)
return my_urls + urls
-
class RouteAdmin(MarkerAdmin):
"""
Specialized the Route field.
@@ -294,10 +312,10 @@ class RouteAdmin(MarkerAdmin):
def queryset(self, request):
qs = self.model._default_manager.get_query_set()
if not request.user.is_superuser:
- areas = get_areas_for_user(request.user)
+ maps = get_maps_for_user(request.user)
contained = Q()
- for area in areas:
- contained = contained | area.getIncludeRoute()
+ for map in maps:
+ contained = contained | map.getIncludeRoute()
qs = qs.filter(contained)
ordering = self.ordering or ()
if ordering:
@@ -312,16 +330,24 @@ class RouteAdmin(MarkerAdmin):
Route.objects.filter(pk=item_id))
class LayerInline(admin.TabularInline):
- model = AreaLayers
+ model = MapLayers
+ extra = 1
+
+class UserInline(admin.TabularInline):
+ model = MapUsers
+ extra = 1
+
+class GroupInline(admin.TabularInline):
+ model = MapGroups
extra = 1
-class AreaAdmin(admin.ModelAdmin):
+class MapAdmin(admin.ModelAdmin):
"""
- Specialized the area field.
+ Specialized the map field.
"""
- form = AreaAdminForm
+ form = MapAdminForm
exclude = ['upper_left_corner', 'lower_right_corner']
- inlines = [LayerInline]
+ inlines = [UserInline, GroupInline, LayerInline]
list_display = ['name', 'order', 'available', 'default']
def importing(modeladmin, request, queryset):
@@ -396,7 +422,6 @@ class ImporterAdmin(admin.ModelAdmin):
readonly_fields = ('state',)
actions = [importing, cancel_import, export_to_osm, cancel_export]
inlines = [ImporterKeyInline]
-admin.site.register(Importer, ImporterAdmin)
class PageAdmin(admin.ModelAdmin):
"""
@@ -441,15 +466,25 @@ class PropertyModelChoiceInline(admin.TabularInline):
class PropertyModelAdmin(admin.ModelAdmin):
inlines = [PropertyModelChoiceInline]
-# register of differents database fields
-admin.site.register(Page, PageAdmin)
-admin.site.register(News, NewsAdmin)
-admin.site.register(Category, CategoryAdmin)
-admin.site.register(Icon, IconAdmin)
-admin.site.register(Marker, MarkerAdmin)
-admin.site.register(Route, RouteAdmin)
-if not settings.CHIMERE_HIDE_PROPERTYMODEL:
+# only register if not yet registered: this mecanism allow to specialized admin
+# in projects
+registered_models = admin.site._registry.keys()
+for model, modeladmin in ((Importer, ImporterAdmin),
+ (Page, PageAdmin),
+ (News, NewsAdmin),
+ (Category, CategoryAdmin),
+ (Icon, IconAdmin),
+ (Marker, MarkerAdmin),
+ (Route, RouteAdmin),
+ (Map, MapAdmin),
+ (ColorTheme, ColorThemeAdmin),
+ (Layer, None)):
+ if model not in registered_models:
+ if modeladmin:
+ admin.site.register(model, modeladmin)
+ else:
+ admin.site.register(model)
+
+if PropertyModel not in registered_models and \
+ not settings.CHIMERE_HIDE_PROPERTYMODEL:
admin.site.register(PropertyModel, PropertyModelAdmin)
-admin.site.register(Area, AreaAdmin)
-admin.site.register(ColorTheme, ColorThemeAdmin)
-admin.site.register(Layer)
diff --git a/chimere/feeds.py b/chimere/feeds.py
index 53444ac..2a62da3 100644
--- a/chimere/feeds.py
+++ b/chimere/feeds.py
@@ -29,7 +29,7 @@ from django.utils.translation import ugettext as _
from django.shortcuts import get_object_or_404
-from chimere.models import Category, SubCategory, Marker, Area
+from chimere.models import Category, SubCategory, Marker, Map
class BaseFeed(Feed):
"""
@@ -64,7 +64,7 @@ class LatestPOIsByCategory(BaseFeed):
title_template = "chimere/feeds/rss_title.html"
description_template = "chimere/feeds/rss_descr.html"
- def get_object(self, request, category_id, area_name=''):
+ def get_object(self, request, category_id, map_name=''):
return get_object_or_404(Category, id=category_id)
def title(self, obj):
@@ -99,7 +99,7 @@ class LatestPOIsBySubCategory(BaseFeed):
title_template = "chimere/feeds/rss_title.html"
description_template = "chimere/feeds/rss_descr.html"
- def get_object(self, request, category_id, area_name=''):
+ def get_object(self, request, category_id, map_name=''):
return get_object_or_404(SubCategory, id=category_id)
def title(self, obj):
@@ -149,7 +149,7 @@ class LatestPOIsByZone(BaseFeed):
lower_right_lat = 0
lower_right_lon = 0
- def get_object(self, request, area, area_name=''):
+ def get_object(self, request, area, map_name=''):
"""
Get the extra url. Parameters are the coordinates of the zone (the
upper left and lower right points)
@@ -207,8 +207,8 @@ class LatestPOIsByZoneID(BaseFeed):
title_template = "chimere/feeds/rss_title.html"
description_template = "chimere/feeds/rss_descr.html"
- def get_object(self, request, area_id, area_name=''):
- return get_object_or_404(Area, id=area_id)
+ def get_object(self, request, map_id, map_name=''):
+ return get_object_or_404(Map, id=map_id)
def title(self, obj):
return settings.PROJECT_NAME + u" - " + \
@@ -217,7 +217,7 @@ class LatestPOIsByZoneID(BaseFeed):
def link(self, obj):
if not obj:
raise FeedDoesNotExist
- return reverse('chimere:feeds-areaid', args=['', obj.id])
+ return reverse('chimere:feeds-mapid', args=['', obj.id])
def items(self, obj):
q = Marker.objects.filter(available_date__isnull=False, status='A')
diff --git a/chimere/fixtures/initial_data.json b/chimere/fixtures/initial_data.json
index c44eb63..cff5710 100644
--- a/chimere/fixtures/initial_data.json
+++ b/chimere/fixtures/initial_data.json
@@ -189,37 +189,5 @@
"multimedia_type": 13,
"name": "webm"
}
- },
- {
- "pk": 1,
- "model": "chimere.layer",
- "fields": {
- "layer_code": "new OpenLayers.Layer.OSM.Mapnik(\"Mapnik OSM\",\r\n{attribution:\"\", keyid:\"mapnik\", displayOutsideMaxExtent:!0,wrapDateLine:!0,layerCode:\"M\"})",
- "name": "OSM - Mapnik"
- }
- },
- {
- "pk": 2,
- "model": "chimere.layer",
- "fields": {
- "layer_code": "new OpenLayers.Layer.OSM.CycleMap(\"Cycle map\",\r\n{attribution:\"Tiles courtesy of <a href='http://www.opencyclemap.org/' target='_blank'>Andy Allan</a>\",\r\nkeyid:\"cyclemap\",displayOutsideMaxExtent:!0,wrapDateLine:!0,layerCode:\"C\"})",
- "name": "OSM - CycleMap"
- }
- },
- {
- "pk": 3,
- "model": "chimere.layer",
- "fields": {
- "layer_code": "new OpenLayers.Layer.OSM.TransportMap(\"Transport map\",\r\n{attribution:\"Tiles courtesy of <a href='http://www.opencyclemap.org/' target='_blank'>Andy Allan</a>\",\r\nkeyid:\"transportmap\",displayOutsideMaxExtent:!0,wrapDateLine:!0,layerCode:\"T\"})",
- "name": "OSM - TransportMap"
- }
- },
- {
- "pk": 4,
- "model": "chimere.layer",
- "fields": {
- "layer_code": "new OpenLayers.Layer.MapQuestOSM()",
- "name": "OSM - MapQuest"
- }
}
]
diff --git a/chimere/fixtures/layers-default-openlayers.json b/chimere/fixtures/layers-default-openlayers.json
new file mode 100644
index 0000000..6ab3f3e
--- /dev/null
+++ b/chimere/fixtures/layers-default-openlayers.json
@@ -0,0 +1,34 @@
+[
+ {
+ "pk": 1,
+ "model": "chimere.layer",
+ "fields": {
+ "layer_code": "new OpenLayers.Layer.OSM.Mapnik(\"Mapnik OSM\",\r\n{attribution:\"\", keyid:\"mapnik\", displayOutsideMaxExtent:!0,wrapDateLine:!0,layerCode:\"M\"})",
+ "name": "OSM - Mapnik"
+ }
+ },
+ {
+ "pk": 2,
+ "model": "chimere.layer",
+ "fields": {
+ "layer_code": "new OpenLayers.Layer.OSM.CycleMap(\"Cycle map\",\r\n{attribution:\"Tiles courtesy of <a href='http://www.opencyclemap.org/' target='_blank'>Andy Allan</a>\",\r\nkeyid:\"cyclemap\",displayOutsideMaxExtent:!0,wrapDateLine:!0,layerCode:\"C\"})",
+ "name": "OSM - CycleMap"
+ }
+ },
+ {
+ "pk": 3,
+ "model": "chimere.layer",
+ "fields": {
+ "layer_code": "new OpenLayers.Layer.OSM.TransportMap(\"Transport map\",\r\n{attribution:\"Tiles courtesy of <a href='http://www.opencyclemap.org/' target='_blank'>Andy Allan</a>\",\r\nkeyid:\"transportmap\",displayOutsideMaxExtent:!0,wrapDateLine:!0,layerCode:\"T\"})",
+ "name": "OSM - TransportMap"
+ }
+ },
+ {
+ "pk": 4,
+ "model": "chimere.layer",
+ "fields": {
+ "layer_code": "new OpenLayers.Layer.MapQuestOSM()",
+ "name": "OSM - MapQuest"
+ }
+ }
+]
diff --git a/chimere/forms.py b/chimere/forms.py
index c8d7d45..d923963 100644
--- a/chimere/forms.py
+++ b/chimere/forms.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
-# Copyright (C) 2008-2014 Étienne Loks <etienne.loks_AT_peacefrogsDOTnet>
+# Copyright (C) 2008-2015 Étienne Loks <etienne.loks_AT_peacefrogsDOTnet>
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as
@@ -34,8 +34,7 @@ from django.core.mail import EmailMessage, BadHeaderError
if hasattr(settings, 'CHIMERE_SEARCH_ENGINE') and settings.CHIMERE_SEARCH_ENGINE:
from haystack.forms import SearchForm as HaystackSearchForm
-
-from chimere.models import Marker, Route, PropertyModel, Property, Area,\
+from chimere.models import Marker, Route, PropertyModel, Property, Map,\
News, Category, SubCategory, RouteFile, MultimediaFile, MultimediaType, \
PictureFile, Importer, PropertyModelChoice, IFRAME_LINKS, \
MultimediaExtension, Page, IMPORTER_CHOICES
@@ -261,10 +260,13 @@ class MarkerAdminFormBase(forms.ModelForm):
"""
Custom save method in order to manage associated properties
"""
+ can_write = False
+ if 'can_write' in keys:
+ can_write = keys.pop('can_write')
new_marker = super(MarkerAdminFormBase, self).save(*args, **keys)
if 'status' not in self.cleaned_data and not new_marker.status:
- new_marker.status = 'S'
- if new_marker.status == 'A':
+ new_marker.status = 'A' if can_write else 'S'
+ if not new_marker.available_date and new_marker.status == 'A':
tz = UTC()
new_marker.available_date = datetime.replace(datetime.utcnow(),
tzinfo=tz)
@@ -351,9 +353,12 @@ class RouteAdminForm(forms.ModelForm):
"""
Custom save method in order to manage associated properties
"""
+ can_write = False
+ if 'can_write' in keys:
+ can_write = keys.pop('can_write')
new_route = super(RouteAdminForm, self).save(*args, **keys)
if 'status' not in self.cleaned_data and not new_route.status:
- new_route.status = 'S'
+ new_route.status = 'A' if can_write else 'S'
new_route.save()
return new_route
@@ -520,15 +525,15 @@ class FullFileForm(FileForm):
super(FullFileForm, self).__init__(*args, **kwargs)
self.fields.keyOrder = ['name', 'raw_file']
-class AreaAdminForm(forms.ModelForm):
+class MapAdminForm(forms.ModelForm):
"""
- Admin page to create an area
+ Admin page to create an map
"""
area = AreaField(label=_("Area"), fields=(PointField(), PointField()))
welcome_message = forms.CharField(widget=TextareaAdminWidget,
required=False)
class Meta:
- model = Area
+ model = Map
def __init__(self, *args, **keys):
"""
@@ -553,7 +558,7 @@ class AreaAdminForm(forms.ModelForm):
keys['initial'].update(dct)
else:
keys['initial'] = dct
- super(AreaAdminForm, self).__init__(*args, **keys)
+ super(MapAdminForm, self).__init__(*args, **keys)
def clean(self):
'''
@@ -567,7 +572,7 @@ class AreaAdminForm(forms.ModelForm):
msg = _(u"No area selected.")
raise forms.ValidationError(msg)
if self.cleaned_data.get('order'):
- q = Area.objects.filter(order=self.cleaned_data.get('order'))
+ q = Map.objects.filter(order=self.cleaned_data.get('order'))
if self.instance:
q = q.exclude(pk=self.instance.pk)
if q.count():
@@ -580,15 +585,15 @@ class AreaAdminForm(forms.ModelForm):
"""
Custom save method in order to manage area
"""
- new_area = super(AreaAdminForm, self).save(*args, **keys)
+ new_area = super(MapAdminForm, self).save(*args, **keys)
area = self.cleaned_data['area']
new_area.upper_left_corner = 'POINT(%s %s)' % (area[0][0], area[0][1])
new_area.lower_right_corner = 'POINT(%s %s)' % (area[1][0],
area[1][1])
content_type = ContentType.objects.get(app_label="chimere",
- model="area")
+ model="map")
if new_area.urn:
- mnemo = 'change_area_' + new_area.urn
+ mnemo = 'change_map_' + new_area.urn
perm = Permission.objects.filter(codename=mnemo)
if not perm:
perm = Permission(name='Can change ' + new_area.name,
@@ -596,18 +601,18 @@ class AreaAdminForm(forms.ModelForm):
perm.save()
else:
if 'urn' in self.initial:
- mnemo = 'change_area_' + self.initial['urn']
+ mnemo = 'change_map_' + self.initial['urn']
perm = Permission.objects.filter(codename=mnemo)
if perm:
perm[0].delete()
return new_area
-class AreaForm(AreaAdminForm):
+class MapForm(MapAdminForm):
"""
Form for the edit page
"""
class Meta:
- model = Area
+ model = Map
CHIMERE_ROUTING_TRANSPORT = []
ROUTING_INIT = None
diff --git a/chimere/locale/fr/LC_MESSAGES/django.po b/chimere/locale/fr/LC_MESSAGES/django.po
index 4634b0a..913997d 100644
--- a/chimere/locale/fr/LC_MESSAGES/django.po
+++ b/chimere/locale/fr/LC_MESSAGES/django.po
@@ -7,125 +7,129 @@ msgid ""
msgstr ""
"Project-Id-Version: 0.2\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2015-02-26 01:30+0100\n"
+"POT-Creation-Date: 2013-10-27 20:17+0100\n"
"PO-Revision-Date: 2010-03-20 20:00+0100\n"
"Last-Translator: Étienne Loks <etienne.loks@peacefrogs.net>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-#: actions.py:39
+#: actions.py:41
msgid "View"
msgstr "Voir"
-#: actions.py:40
+#: actions.py:42
msgid "Contribute"
msgstr "Participer"
-#: actions.py:41
+#: actions.py:46
msgid "Add a new point of interest"
msgstr "Ajout d'un point remarquable"
-#: actions.py:42
+#: actions.py:47
msgid "Add a new route"
msgstr "Ajout d'un nouveau trajet"
-#: actions.py:47
+#: actions.py:53
msgid "RSS feeds"
msgstr "Flux RSS"
-#: actions.py:51
+#: actions.py:57
msgid "Contact us"
msgstr "Nous contacter"
-#: admin.py:54
+#: admin.py:56 admin.py:57
+msgid "User"
+msgstr "Utilisateur"
+
+#: admin.py:69
msgid "Disable"
msgstr "Désactiver"
-#: admin.py:60 templates/admin/chimere/managed_modified.html:44
+#: admin.py:75 templates/admin/chimere/managed_modified.html:44
#: templates/chimere/feeds/rss.html:70
msgid "Validate"
msgstr "Valider"
-#: admin.py:71
+#: admin.py:86
msgid "Export to KML"
msgstr "Exporter en KML"
-#: admin.py:85
+#: admin.py:100
msgid "Export to Shapefile"
msgstr "Exporter en Shapefile"
-#: admin.py:95
+#: admin.py:114
msgid "Export to CSV"
msgstr "Exporter en CSV"
-#: admin.py:102
+#: admin.py:121
msgid "Only one item can be managed at a time."
msgstr "Seul un élément à la fois peut-être géré."
-#: admin.py:112
+#: admin.py:131
msgid "No modified item associated to the selected item."
msgstr "Pas d'élément modifié associé à l'élément sélectionné."
-#: admin.py:158
+#: admin.py:177
msgid "Modified item traited."
msgstr "Élément modifié traité."
-#: admin.py:163
+#: admin.py:182
msgid "Managed modified items"
msgstr "Gérer les éléments modifiés"
-#: admin.py:197 admin.py:275
+#: admin.py:216 admin.py:292
msgid "Submitter"
msgstr "Demandeur"
-#: admin.py:202 admin.py:280 admin.py:332
+#: admin.py:221 admin.py:297 admin.py:357
msgid "Import"
msgstr "Import"
-#: admin.py:207 admin.py:285
+#: admin.py:226 admin.py:302
msgid "Associated items"
msgstr "Éléments associés"
-#: admin.py:338
+#: admin.py:363
msgid "Cancel import"
msgstr "Annuler l'import"
-#: admin.py:344
+#: admin.py:369
msgid "Cancel export"
msgstr "Annuler l'export"
-#: admin.py:348
+#: admin.py:373
msgid "Can manage only one OSM export at a time."
-msgstr "Ne peux gérer qu'un seul export OSM à la fois."
+msgstr "Ne peut gérer qu'un seul export OSM à la fois."
-#: admin.py:353
+#: admin.py:378
msgid ""
"You must treat all item with the status \"imported\" before exporting to OSM."
msgstr ""
"Vous devez traiter tous les éléments avec le status « importé » avant "
"d'exporter vers OSM."
-#: admin.py:357
+#: admin.py:382
msgid "Only OSM importer are managed for export."
msgstr "Seul les imports de type OSM peuvent être gérés pour les exports."
-#: admin.py:364
+#: admin.py:389
msgid "No point of interest are concerned by this export."
msgstr "Aucun point d'intérêt n'est concerné par cet export."
-#: admin.py:376
+#: admin.py:401
msgid "Export launched."
msgstr "Export lancé."
-#: admin.py:380
+#: admin.py:405
#, python-format
msgid ""
"%s point(s) of interest concerned by this export before bounding box filter."
msgstr ""
"%s point(s) d'intérêt concerné par cet export (avant le filtre sur la zone)"
-#: admin.py:385
+#: admin.py:410
msgid "Export to osm"
msgstr "Exporter vers osm"
@@ -141,50 +145,50 @@ msgstr "Nouveaux points d'intérêt de "
msgid "Last points of interest by area"
msgstr "Nouveaux points d'intérêt par zone"
-#: forms.py:87
+#: forms.py:83
msgid "New submission for"
msgstr "Nouvelle proposition pour"
-#: forms.py:88
+#: forms.py:84
#, python-format
msgid "The new item \"%s\" has been submited in the category: "
msgstr "Le nouvel élément « %s » a été proposé dans la catégorie : "
-#: forms.py:90
+#: forms.py:86
msgid "To valid, precise or unvalid this item: "
msgstr "Pour valider, préciser ou rejeter cet élément : "
-#: forms.py:100
+#: forms.py:96
msgid "Email (optional)"
msgstr "Courriel (optionnel) "
-#: forms.py:101
+#: forms.py:97
msgid "Object"
msgstr "Objet"
-#: forms.py:121
+#: forms.py:117
msgid "OSM user"
msgstr "Utilisateur OSM"
-#: forms.py:122 models.py:1543
+#: forms.py:118 models.py:1635
msgid "Password"
msgstr "Mot de passe"
-#: forms.py:126
+#: forms.py:122
msgid "API"
msgstr "API"
-#: forms.py:129
+#: forms.py:125
#, python-format
msgid "Test API - %s"
msgstr "API de test - %s"
-#: forms.py:131
+#: forms.py:127
#, python-format
msgid "Main API - %s"
msgstr "API principale - %s"
-#: forms.py:162 forms.py:166
+#: forms.py:158 forms.py:162
msgid ""
"For OSM import you must be provide a filter. Select an area and node/way "
"filter."
@@ -192,30 +196,30 @@ msgstr ""
"Pour les imports OSM vous devez fournir un filtre. Sélectionnez une zone et "
"un filtre sur les nœuds/routes."
-#: forms.py:170
+#: forms.py:166
msgid "Shapefiles must be provided in a zipped archive."
msgstr ""
"Les fichiers Shapefiles doivent être fournis regroupés dans une archive zip."
-#: forms.py:175
+#: forms.py:170
msgid "You have to set \"source\" or \"source file\" but not both."
msgstr ""
"Vous devez spécifier le champ « Source » ou « Fichier source » mais pas les "
"deux."
-#: forms.py:180
+#: forms.py:175
msgid "You have to set \"source\" or \"source file\"."
msgstr "Vous devez spécifier le champ « Source » ou « Fichier source »."
-#: forms.py:241
+#: forms.py:236
msgid "End date has been set with no start date"
msgstr "Une date de fin a été donnée sans date de début"
-#: forms.py:245
+#: forms.py:240
msgid "End date can't be before start date"
msgstr "La date de fin ne peut pas être antérieure à la date de début"
-#: forms.py:255
+#: forms.py:250
msgid "This field is mandatory for the selected categories"
msgstr "Ce champ est obligatoire pour les catégories sélectionnées"
@@ -227,16 +231,16 @@ msgstr "Fichier"
msgid "Bad file format: this must be a GPX or KML file"
msgstr "Mauvais format de fichier : KML et GPX sont supportés"
-#: forms.py:518 models.py:54 models.py:102 models.py:164 models.py:185
-#: models.py:198 models.py:213 models.py:414 models.py:764 models.py:820
-#: models.py:879 models.py:997 models.py:1347 models.py:1359 models.py:1533
-#: utils.py:490 templates/admin/chimere/managed_modified.html:23
-#: templates/chimere/edit.html:40 templates/chimere/edit_route.html:37
+#: forms.py:518 models.py:53 models.py:101 models.py:163 models.py:185
+#: models.py:198 models.py:213 models.py:378 models.py:752 models.py:808
+#: models.py:867 models.py:985 models.py:1343 models.py:1355 models.py:1624
+#: utils.py:487 templates/admin/chimere/managed_modified.html:23
+#: templates/chimere/edit.html:44 templates/chimere/edit_route.html:39
#: templates/chimere/blocks/alternate_multimedia.html:39
msgid "Name"
msgstr "Nom"
-#: forms.py:527 models.py:1396
+#: forms.py:527
msgid "Area"
msgstr "Zone"
@@ -262,62 +266,62 @@ msgstr "Arrivée"
msgid "Speed"
msgstr "Vitesse"
-#: models.py:55
+#: models.py:54
msgid "Mnemonic"
msgstr "Mnémonique"
-#: models.py:57 models.py:103 models.py:186 models.py:214 models.py:407
-#: models.py:768 models.py:1365 models.py:1535 models.py:1578
+#: models.py:56 models.py:102 models.py:186 models.py:214 models.py:371
+#: models.py:756 models.py:1356 models.py:1626 models.py:1673
msgid "Available"
msgstr "Disponible"
-#: models.py:58 models.py:174 models.py:187 models.py:231 models.py:822
-#: models.py:894 models.py:1364 models.py:1522 models.py:1534
+#: models.py:57 models.py:173 models.py:187 models.py:233 models.py:810
+#: models.py:882 models.py:1362 models.py:1613 models.py:1625
msgid "Order"
msgstr "Ordre"
-#: models.py:59
+#: models.py:58
msgid "Template path"
msgstr "Chemin du patron"
-#: models.py:66 models.py:67
+#: models.py:65 models.py:66
msgid "Page"
msgstr "Page"
-#: models.py:104 models.py:510
+#: models.py:103 models.py:474
msgid "Is front page"
msgstr "Est en page principale"
-#: models.py:106 models.py:1544
+#: models.py:105 models.py:1636
msgid "Date"
msgstr "Date"
-#: models.py:108 models.py:821
+#: models.py:107 models.py:809
msgid "Url"
msgstr "Url"
-#: models.py:109
-msgid "Associated areas"
-msgstr "Zones associées"
+#: models.py:108
+msgid "Associated maps"
+msgstr "Cartes associées"
-#: models.py:115 models.py:116 templates/chimere/blocks/news.html:3
+#: models.py:114 models.py:115 templates/chimere/blocks/news.html:3
#: templates/chimere/blocks/news.html:5
msgid "News"
msgstr "Nouvelle"
-#: models.py:125
+#: models.py:124
msgid "Parameters"
msgstr "Paramètres"
-#: models.py:129
+#: models.py:128
msgid "TinyUrl"
msgstr "Mini-url"
-#: models.py:168 models.py:175 models.py:226
+#: models.py:167 models.py:174 models.py:228
msgid "Color theme"
msgstr "Thème de couleur"
-#: models.py:173
+#: models.py:172
msgid "Code"
msgstr "Code"
@@ -325,24 +329,24 @@ msgstr "Code"
msgid "Color"
msgstr "Couleur"
-#: models.py:193 models.py:211 models.py:400
+#: models.py:193 models.py:211
msgid "Category"
msgstr "Catégorie"
-#: models.py:199 models.py:760 models.py:880 models.py:1063
+#: models.py:199 models.py:748 models.py:868 models.py:1051
#: templates/chimere/blocks/alternate_multimedia.html:43
msgid "Image"
msgstr "Image"
-#: models.py:201 models.py:882 models.py:1065
+#: models.py:201 models.py:870 models.py:1053
msgid "Height"
msgstr "Hauteur"
-#: models.py:202 models.py:883 models.py:1066
+#: models.py:202 models.py:871 models.py:1054
msgid "Width"
msgstr "Largeur"
-#: models.py:206 models.py:223
+#: models.py:206 models.py:225
msgid "Icon"
msgstr "Icône"
@@ -351,395 +355,379 @@ msgid "Available for submission"
msgstr "Disponible pour soumission"
#: models.py:217
+msgid "Has an associated quantity"
+msgstr "A une quantité associée"
+
+#: models.py:219
msgid "Marker"
msgstr "Point d'intérêt"
-#: models.py:218 models.py:1059 models.py:1076
-#: templates/chimere/edit_route.html:28
+#: models.py:220 models.py:1047 models.py:1064
+#: templates/chimere/edit_route.html:30
msgid "Route"
msgstr "Trajet"
-#: models.py:219
+#: models.py:221
msgid "Both"
msgstr "Mixte"
-#: models.py:220
+#: models.py:222
msgid "Item type"
msgstr "Type d'élément"
-#: models.py:221
+#: models.py:223
msgid "Is dated"
msgstr "Est daté"
-#: models.py:224
+#: models.py:226
msgid "Hover icon"
msgstr "Icône en survol"
-#: models.py:228
+#: models.py:230
msgid "Displayed in the layer menu"
msgstr "Apparaît dans le menu des couches ?"
-#: models.py:230
+#: models.py:232
msgid "Routing warn"
msgstr "Avertissement sur les itinéraires"
-#: models.py:236
+#: models.py:238
msgid "Sub-category"
msgstr "Sous-catégorie"
-#: models.py:237
+#: models.py:239
msgid "Sub-categories"
msgstr "Sous-catégories"
-#: models.py:326
+#: models.py:323
msgid "Importer type"
msgstr "Type d'import"
-#: models.py:328
+#: models.py:325
msgid "Filter"
msgstr "Filtre"
-#: models.py:330 templates/chimere/blocks/alternate_multimedia.html:49
+#: models.py:327 templates/chimere/blocks/alternate_multimedia.html:49
msgid "Web address"
msgstr "Adresse web"
-#: models.py:332
-msgid "Don't forget the trailing slash"
-msgstr "N'oubliez pas la barre oblique (« / ») finale"
-
-#: models.py:333
+#: models.py:329
msgid "Source file"
msgstr "Fichier source"
-#: models.py:335
-msgid "Alt source file"
-msgstr "Fichier source alternatif"
-
-#: models.py:337
+#: models.py:331
msgid "Name by default"
msgstr "Nom par défaut"
-#: models.py:339
+#: models.py:333
msgid "SRID"
msgstr "SRID"
-#: models.py:340
+#: models.py:334
msgid "Zipped file"
msgstr "Fichier zippé"
-#: models.py:341
+#: models.py:335
msgid "Overwrite existing data"
msgstr "Écraser les données existantes"
-#: models.py:343
+#: models.py:337
msgid "Get description from source"
msgstr "Obtenir une description depuis la source"
-#: models.py:345
+#: models.py:339
msgid "Default description"
msgstr "Description par défaut"
-#: models.py:347 models.py:437
+#: models.py:341 models.py:399
msgid "Origin"
msgstr "Origine"
-#: models.py:349 models.py:439
+#: models.py:343 models.py:401
msgid "License"
msgstr "Licence"
-#: models.py:352
+#: models.py:346
msgid "Associated subcategories"
msgstr "Sous-catégories associées"
-#: models.py:353 utils.py:494
+#: models.py:347 utils.py:491
msgid "State"
msgstr "État"
-#: models.py:354
+#: models.py:349
msgid "Automatically associate a marker to a way"
msgstr "Associer automatiquement un marqueur à une route"
-#: models.py:356
-msgid "Automatically updated"
-msgstr "Mis à jour automatiquement"
-
-#: models.py:358
-msgid "Default localisation"
-msgstr "Localisation par défaut"
-
-#: models.py:364 models.py:398
+#: models.py:353
msgid "Importer"
msgstr "Import"
-#: models.py:401 models.py:427
-msgid "Import key"
-msgstr "Clé d'import"
-
-#: models.py:404
-msgid "Importer - Key categories"
-msgstr "Importeur - clés / catégories"
-
-#: models.py:406
+#: models.py:370
msgid "Submited"
msgstr "Soumis"
-#: models.py:408
+#: models.py:372
msgid "Modified"
msgstr "Modifié"
-#: models.py:409
+#: models.py:373
msgid "Disabled"
msgstr "Désactivé"
-#: models.py:410
+#: models.py:374
msgid "Imported"
msgstr "Importé"
-#: models.py:416
+#: models.py:380
msgid "Submitter session key"
msgstr "Clé de session du demandeur"
-#: models.py:418
+#: models.py:382
msgid "Submitter name or nickname"
msgstr "Nom ou pseudo du demandeur"
-#: models.py:420
+#: models.py:384
msgid "Submitter email"
msgstr "Courriel du demandeur"
-#: models.py:422
+#: models.py:386
msgid "Submitter comment"
msgstr "Commentaire du demandeur"
-#: models.py:424 models.py:1239
+#: models.py:388 models.py:1234
msgid "Status"
msgstr "État"
-#: models.py:425 templates/chimere/edit.html:56
-msgid "Keywords"
-msgstr "Mots clés"
+#: models.py:389
+msgid "Import key"
+msgstr "Clé d'import"
-#: models.py:429
+#: models.py:391
msgid "Import version"
msgstr "Version de l'import"
-#: models.py:431
+#: models.py:393
msgid "Source"
msgstr "Source"
-#: models.py:433
+#: models.py:395
msgid "Modified since last import"
msgstr "Modifié depuis le dernier import"
-#: models.py:435
+#: models.py:397
msgid "Not to be exported to OSM"
msgstr "À ne pas exporter vers OSM"
-#: models.py:441 templates/chimere/edit.html:63
-#: templates/chimere/edit_route.html:53
+#: models.py:403 templates/chimere/edit.html:61
+#: templates/chimere/edit_route.html:55
msgid "Start date"
msgstr "Date de début"
-#: models.py:442
+#: models.py:404
msgid "Not mandatory. Set it for dated item such as event. Format YYYY-MM-DD"
msgstr ""
"Optionnel. Précisez ce champ pour les éléments datés comme un événement. "
"Format du champ : AAAA-MM-JJ"
-#: models.py:444 templates/chimere/edit.html:69
-#: templates/chimere/edit_route.html:59
+#: models.py:406 templates/chimere/edit.html:67
+#: templates/chimere/edit_route.html:61
msgid "End date"
msgstr "Date de fin"
-#: models.py:445
+#: models.py:407
msgid ""
"Not mandatory. Set it only if you have a multi-day event. Format YYYY-MM-DD"
msgstr ""
"Optionnel. Précisez ce champ seulement pour des événements durant plusieurs "
"jours. Format du champ : AAAA-MM-JJ"
-#: models.py:502
+#: models.py:464
msgid "Reference marker"
msgstr "Point d'intérêt de référence"
-#: models.py:503 utils.py:496
+#: models.py:465 utils.py:493
msgid "Localisation"
msgstr "Localisation"
-#: models.py:505
+#: models.py:467
msgid "Available Date"
msgstr "Date de mise en disponibilité"
-#: models.py:509 utils.py:495 templates/admin/chimere/managed_modified.html:31
-#: templates/chimere/edit.html:50 templates/chimere/edit_route.html:47
+#: models.py:471
+msgid "Quantity"
+msgstr "Quantité"
+
+#: models.py:473 utils.py:492 templates/admin/chimere/managed_modified.html:31
+#: templates/chimere/edit.html:54 templates/chimere/edit_route.html:49
msgid "Description"
msgstr "Description"
-#: models.py:580 models.py:1588
+#: models.py:548 models.py:1683
msgid "Point of interest"
msgstr "Point d'intérêt"
-#: models.py:758
+#: models.py:746
msgid "Audio"
msgstr "Audio"
-#: models.py:759
+#: models.py:747
msgid "Video"
msgstr "Vidéo"
-#: models.py:761
+#: models.py:749
msgid "Other"
msgstr "Autre"
-#: models.py:762
+#: models.py:750
msgid "Media type"
msgstr "Type de media"
-#: models.py:765
+#: models.py:753
msgid "Mime type"
msgstr "Type mime"
-#: models.py:767
+#: models.py:755
msgid "Inside an iframe"
msgstr "À l'intérieur d'un iframe"
-#: models.py:771
+#: models.py:759
msgid "Multimedia type"
msgstr "Type de multimedia"
-#: models.py:772
+#: models.py:760
msgid "Multimedia types"
msgstr "Types de multimedia"
-#: models.py:781
+#: models.py:769
msgid "Automatic recognition"
msgstr "Reconnaissance automatique"
-#: models.py:807
+#: models.py:795
msgid "Extension name"
msgstr "Nom de l'extension"
-#: models.py:809
+#: models.py:797
msgid "Associated multimedia type"
msgstr "Type de multimedia associé"
-#: models.py:813
+#: models.py:801
msgid "Multimedia extension"
msgstr "Extension multimedia"
-#: models.py:814
+#: models.py:802
msgid "Multimedia extensions"
msgstr "Extensions multimedia"
-#: models.py:824 models.py:884
+#: models.py:812 models.py:872
msgid "Display inside the description?"
msgstr "Apparaît dans la description ?"
-#: models.py:829
+#: models.py:817
msgid "Multimedia file"
msgstr "Fichier multimedia"
-#: models.py:830
+#: models.py:818
msgid "Multimedia files"
msgstr "Fichiers multimedias"
-#: models.py:886
+#: models.py:874
msgid "Thumbnail"
msgstr "Miniature"
-#: models.py:890
+#: models.py:878
msgid "Thumbnail height"
msgstr "Hauteur de la miniature"
-#: models.py:892
+#: models.py:880
msgid "Thumbnail width"
msgstr "Largeur de la miniature"
-#: models.py:901
+#: models.py:889
msgid "Picture file"
msgstr "Fichier d'image"
-#: models.py:902
+#: models.py:890
msgid "Picture files"
msgstr "Fichiers d'image"
-#: models.py:998
+#: models.py:986
msgid "Raw file (gpx or kml)"
msgstr "Fichier brut (gpx ou kml)"
-#: models.py:1000
+#: models.py:988
msgid "Simplified file"
msgstr "Fichier simplifié"
-#: models.py:1002
+#: models.py:990
msgid "KML"
msgstr "KML"
-#: models.py:1002
+#: models.py:990
msgid "GPX"
msgstr "GPX"
-#: models.py:1007
+#: models.py:995
msgid "Route file"
msgstr "Fichier de trajet"
-#: models.py:1008
+#: models.py:996
msgid "Route files"
msgstr "Fichiers de trajet"
-#: models.py:1058
+#: models.py:1046
msgid "Reference route"
msgstr "Trajet de référence"
-#: models.py:1062
+#: models.py:1050
msgid "Associated file"
msgstr "Fichier associé"
-#: models.py:1067
+#: models.py:1055
msgid "Has an associated marker"
msgstr "Dispose d'un marqueur associé"
-#: models.py:1348
+#: models.py:1344
msgid "Layer code"
msgstr "Code pour la couche"
-#: models.py:1354
+#: models.py:1350
msgid "Layer"
msgstr "Couche"
-#: models.py:1360
-msgid "Area urn"
-msgstr "Urn de la zone"
+#: models.py:1358
+msgid "Map urn"
+msgstr "Urn de la carte"
-#: models.py:1362 templates/chimere/blocks/welcome.html:3
+#: models.py:1360 templates/chimere/blocks/welcome.html:3
msgid "Welcome message"
msgstr "Message d'accueil"
-#: models.py:1366
+#: models.py:1363
msgid "Upper left corner"
msgstr "Coin en haut à gauche"
-#: models.py:1368
+#: models.py:1365
msgid "Lower right corner"
msgstr "Coin en bas à droite"
-#: models.py:1370
-msgid "Default area"
-msgstr "Zone par défaut"
+#: models.py:1367
+msgid "Default map"
+msgstr "Carte par défaut"
-#: models.py:1371
-msgid "Only one area is set by default"
-msgstr "Seule une zone est définie par défaut"
+#: models.py:1368
+msgid "Only one map is set by default"
+msgstr "Seule une carte est définie par défaut"
-#: models.py:1375
+#: models.py:1372
msgid "Sub-categories checked by default"
msgstr "Sous-catégories cochées par défaut"
-#: models.py:1377
+#: models.py:1374
msgid "Sub-categories dynamicaly displayed"
msgstr "Sous-categories affichées dynamiquement"
-#: models.py:1378
+#: models.py:1375
msgid ""
"If checked, categories are only displayed in the menu if they are available "
"on the current extent."
@@ -747,106 +735,146 @@ msgstr ""
"Si coché, les catégories sont disponibles sur le menu seulement si elles "
"apparaissent sur la zone affichée."
-#: models.py:1382 models.py:1538
+#: models.py:1379 models.py:1630
msgid "Restricted to theses sub-categories"
msgstr "Restreindre à ces sous-categories"
-#: models.py:1383
+#: models.py:1380
msgid "If no sub-category is set all sub-categories are available"
msgstr ""
"Si aucune sous-catégorie n'est définie toutes les sous-catégories sont "
"disponibles"
-#: models.py:1385
+#: models.py:1382
msgid "Link to an external CSS"
msgstr "Lien vers une feuille de style externe"
+#: models.py:1386
+msgid "Public can read the map"
+msgstr "Carte lisible publiquement"
+
#: models.py:1387
+msgid "Public can propose item to the map"
+msgstr "Des propositions de modification peuvent être faites publiquement"
+
+#: models.py:1388
+msgid "Public can write without moderation to the map"
+msgstr "Des modifications peuvent être faites publiquement"
+
+#: models.py:1389
msgid "Restrict to the area extent"
msgstr "Restreindre à l'étendue de la zone"
-#: models.py:1523 widgets.py:89
+#: models.py:1398 templates/chimere/blocks/footer.html:2
+msgid "Map"
+msgstr "Carte"
+
+#: models.py:1593 models.py:1603
+msgid "Can read the map"
+msgstr "Peut lire la carte"
+
+#: models.py:1594 models.py:1604
+msgid "Can propose item to the map"
+msgstr "Peut proposer des éléments sur la carte"
+
+#: models.py:1595 models.py:1605
+msgid "Can write without moderation to the map"
+msgstr "Peut écrire sans modération sur la carte"
+
+#: models.py:1597
+msgid "Map - user"
+msgstr "Carte - Utilisateur"
+
+#: models.py:1598
+msgid "Map - users"
+msgstr "Cartes - Utilisateurs"
+
+#: models.py:1607
+msgid "Map - group"
+msgstr "Carte - Groupe"
+
+#: models.py:1608
+msgid "Map - groups"
+msgstr "Carte - Groupes"
+
+#: models.py:1614 widgets.py:89
msgid "Default layer"
msgstr "Couche par défaut"
-#: models.py:1527 models.py:1528
+#: models.py:1618 models.py:1619
msgid "Layers"
msgstr "Couches"
-#: models.py:1536
+#: models.py:1627
msgid "Mandatory"
msgstr "Obligatoire"
-#: models.py:1539
+#: models.py:1631
msgid ""
"If no sub-category is set all the property applies to all sub-categories"
msgstr ""
"Si aucune sous-catégorie n'est précisée, cette propriété est disponible pour "
"toutes les sous-catégories"
-#: models.py:1541
+#: models.py:1633
msgid "Text"
msgstr "Texte"
-#: models.py:1542
+#: models.py:1634
msgid "Long text"
msgstr "Texte long"
-#: models.py:1545
+#: models.py:1637
msgid "Choices"
msgstr "Choix"
-#: models.py:1546
-msgid "Boolean"
-msgstr "Booléen"
-
-#: models.py:1555
+#: models.py:1645
msgid "Type"
msgstr "Type"
-#: models.py:1560 models.py:1576 models.py:1590
+#: models.py:1650 models.py:1671 models.py:1685
msgid "Property model"
msgstr "Modèle de propriété"
-#: models.py:1577 models.py:1591
+#: models.py:1672 models.py:1686
msgid "Value"
msgstr "Valeur"
-#: models.py:1583
+#: models.py:1678
msgid "Model property choice"
msgstr "Choix pour les modèles de propriété"
-#: models.py:1602
+#: models.py:1701
msgid "Property"
msgstr "Propriété"
-#: settings.sample.py:92
+#: settings.sample.py:86
msgid "Foot"
-msgstr "À pieds"
+msgstr "À pied"
-#: settings.sample.py:93
+#: settings.sample.py:87
msgid "Bicycle"
msgstr "À vélo"
-#: settings.sample.py:94
+#: settings.sample.py:88
msgid "Motorcar"
msgstr "En voiture"
-#: settings.sample.py:97
+#: settings.sample.py:91
msgid "You are walking slowly"
msgstr "Vous marchez lentement"
-#: settings.sample.py:98
+#: settings.sample.py:92
msgid "You are walking pretty quickly"
-msgstr "Vous marchez plutôt rapidement"
+msgstr "Vous marchez assez rapidement"
-#: settings.sample.py:99
+#: settings.sample.py:93
msgid "You are riding pretty slowly"
-msgstr "Vous conduisez plutôt lentement"
+msgstr "Vous roulez plutôt lentement"
-#: settings.sample.py:100
+#: settings.sample.py:94
msgid "You are riding pretty quickly"
-msgstr "Vous conduisez plutôt rapidement"
+msgstr "Vous roulez plutôt rapidement"
#: tasks.py:63
msgid "Import pending"
@@ -898,28 +926,28 @@ msgstr "Export échoué"
msgid "Export canceled"
msgstr "Export annulé"
-#: utils.py:153 utils.py:202
+#: utils.py:150 utils.py:199
msgid "Bad zip file"
msgstr "Mauvais fichier zip"
-#: utils.py:205
+#: utils.py:202
msgid "Missing file(s) inside the zip file"
msgstr "Fichier(s) manquant(s) dans l'archive zip"
-#: utils.py:246
+#: utils.py:243
msgid "Bad XML file"
msgstr "Mauvais fichier XML"
-#: utils.py:333
+#: utils.py:330
msgid "Error while reading the data source."
msgstr "Erreur lors de la lecture de la source."
-#: utils.py:351
+#: utils.py:348
#, python-format
msgid "SRID cannot be guessed. The default SRID (%s) has been used."
msgstr "Le SRID n'a pu être trouvé. Le SRID par défaut (%s) a été utilisé."
-#: utils.py:372
+#: utils.py:369
#, python-format
msgid ""
"Type of geographic item (%s) of this shapefile is not managed by Chimère."
@@ -927,42 +955,44 @@ msgstr ""
"Les types des éléments géographiques (%s) de ce fichier Shapefile ne sont "
"pas gérés par Chimère."
-#: utils.py:392
+#: utils.py:389
msgid "Bad Shapefile"
msgstr "Mauvais fichier Shapefile"
-#: utils.py:434
+#: utils.py:431
msgid "Could not create file!"
msgstr "Ne peut pas créer le fichier !"
-#: utils.py:445
+#: utils.py:442
msgid "Failed to create field"
msgstr "Ne peut pas créer un champ"
-#: utils.py:491 templates/admin/chimere/managed_modified.html:25
-#: templates/chimere/edit.html:45 templates/chimere/edit_route.html:42
-#: templates/chimere/main_map.html:16
+#: utils.py:488 templates/admin/chimere/managed_modified.html:25
+#: templates/chimere/edit.html:49 templates/chimere/edit_route.html:44
+#: templates/chimere/main_map.html:26
#: templates/chimere/main_map_simple.html:10
msgid "Categories"
msgstr "Catégories"
-#: utils.py:524
-msgid "Invalid CSV format"
-msgstr "Fichier CSV non valide"
+#: utils.py:529
+msgid "Invalid CSV format - not enough columns check a reference CSV file"
+msgstr ""
+"Format CSV invalide - pas assez de colonnes - vérifiez sur un fichier CSV de "
+"référence"
-#: utils.py:603
+#: utils.py:630
msgid "RSS feed is not well formed"
msgstr "Flux RSS non valide"
-#: utils.py:679
+#: utils.py:706
msgid "Nothing to import"
msgstr "Rien à importer"
-#: utils.py:763
+#: utils.py:790
msgid "New items imported - validate them before exporting"
msgstr "Nouveaux éléments importés - valider ceux-ci avant d'exporter"
-#: utils.py:765
+#: utils.py:792
msgid ""
"There are items from a former import not yet validated - validate them "
"before exporting"
@@ -970,19 +1000,19 @@ msgstr ""
"Il y a des éléments d'un import précédent pas encore validé - Validez les "
"avant d'exporter"
-#: utils.py:777
+#: utils.py:804
msgid "Bad params - programming error"
msgstr "Mauvais paramètres - erreur de programmation"
-#: utils.py:787
+#: utils.py:814
msgid "Bad param"
msgstr "Mauvais paramètre"
-#: utils.py:802
+#: utils.py:829
msgid "No non ambigious tag is defined in the XAPI request"
msgstr "Pas de tag non ambigü définis dans la requête XAPI"
-#: utils.py:804
+#: utils.py:831
msgid ""
"No bounding box is defined in the XAPI request.If you are sure to manage the "
"entire planet set the bounding box to -180,-90,180,90"
@@ -991,40 +1021,23 @@ msgstr ""
"vouloir lancer la requête sur la planète entière fixez la « bounding box » "
"à -180,-90,180,90"
-#: utils.py:933
-msgid "Source page is unreachable."
-msgstr "La page source est inatteignable"
+#: views.py:161
+msgid "Invalid user or password."
+msgstr "Nom d'utilisateur ou mot de passe incorrect"
-#: utils.py:949
-msgid "The source file is not a valid XSLT file."
-msgstr "Le fichier source n'est pas un fichier XSLT valide"
-
-#: utils.py:961
-msgid "The alt source file is not a valid XSLT file."
-msgstr "Le fichier source alternatif n'est pas un fichier XSLT valide"
-
-#: utils.py:1005
-#, python-format
-msgid ""
-"Names \"%s\" doesn't match existing categories. Modify the import to match "
-"theses names with categories."
-msgstr ""
-"Les noms \"%s\" ne correspondent pas à des catégories existantes. Modifiez "
-"l'import pour faire correspondre ces noms avec des catégories."
-
-#: views.py:301
+#: views.py:315
msgid "There are missing field(s) and/or errors in the submited form."
msgstr "Il y a des champs manquants ou des erreurs dans ce formulaire."
-#: views.py:386
+#: views.py:405
msgid "Bad file. Please check it with an external software."
msgstr "Fichier incohérent. Merci de le vérifier avec un logiciel externe."
-#: views.py:501
+#: views.py:521
msgid "Comments/request on the map"
msgstr "Commentaires/requètes sur la carte"
-#: views.py:504
+#: views.py:524
msgid ""
"Thank you for your contribution. It will be taken into account. If you have "
"left your email you may be contacted soon for more details."
@@ -1033,56 +1046,52 @@ msgstr ""
"laissé votre courriel vous serez peut-être contacté bientôt pour plus de "
"détails."
-#: views.py:508
+#: views.py:528
msgid "Temporary error. Renew your message later."
msgstr "Erreur temporaire. Réenvoyez votre message plus tard."
-#: views.py:687
+#: views.py:722
msgid "No category available in this area."
msgstr "Pas de catégorie disponible sur cette zone."
-#: views.py:800
+#: views.py:822
msgid "Bad geometry"
msgstr "Géométrie incorrecte"
-#: views.py:885
+#: views.py:911
msgid "Incorrect choice in the list"
msgstr "Choix incorrect dans la liste"
-#: widgets.py:243
-msgid "Street, City, Country"
-msgstr "Rue, Commune, Pays"
-
-#: widgets.py:281
+#: widgets.py:272
msgid "Latitude"
msgstr "Latitude"
-#: widgets.py:283
+#: widgets.py:274
msgid "Longitude"
msgstr "Longitude"
-#: widgets.py:326
+#: widgets.py:302
msgid "Invalid point"
msgstr "Point invalide"
-#: widgets.py:382
+#: widgets.py:359
msgid "Creation mode"
msgstr "Mode création"
-#: widgets.py:383
+#: widgets.py:360
msgid "To start drawing the route click on the toggle button: \"Draw\"."
msgstr ""
"Pour commencer le dessin cliquez sur le bouton&nbsp;: «&nbsp;Tracer&nbsp;»."
-#: widgets.py:385
+#: widgets.py:362
msgid "Then click on the map to begin the drawing."
msgstr "Puis cliquez sur la carte pour commencer le dessin."
-#: widgets.py:386
+#: widgets.py:363
msgid "You can add points by clicking again."
msgstr "Vous pouvez ajouter des points en cliquant de nouveau."
-#: widgets.py:387
+#: widgets.py:364
msgid ""
"To finish the drawing double click. When the drawing is finished you can "
"edit it."
@@ -1090,7 +1099,7 @@ msgstr ""
"Pour finir le tracé double-cliquez. Quand le tracé est fini vous pouvez "
"toujours l'éditer."
-#: widgets.py:389
+#: widgets.py:366
msgid ""
"While creating to undo a drawing click again on the toggle button \"Stop "
"drawing\"."
@@ -1098,17 +1107,17 @@ msgstr ""
"En mode création vous pouvez annuler un tracé en appuyant sur le bouton "
"«&nbsp;Arrêter le tracé&nbsp;»."
-#: widgets.py:394
+#: widgets.py:371
msgid "Modification mode"
msgstr "Mode modification"
-#: widgets.py:395
+#: widgets.py:372
msgid "To move a point click on it and drag it to the desired position."
msgstr ""
"Pour bouger un point, cliquez dessus, maintenez le click pour le déposer à "
"la position désirée."
-#: widgets.py:396
+#: widgets.py:373
msgid ""
"To delete a point move the mouse cursor over it and press the \"d\" or \"Del"
"\" key."
@@ -1116,7 +1125,7 @@ msgstr ""
"Pour supprimer un point, mettez le curseur de la souris sur celui-ci et "
"appuyez sur le touche «&nbsp;d&nbsp;» ou «&nbsp;Suppr&nbsp;»."
-#: widgets.py:398
+#: widgets.py:375
msgid ""
"To add a point click in the middle of a segment and drag the new point to "
"the desired position"
@@ -1125,51 +1134,51 @@ msgstr ""
"maintenez le bouton appuyé et déplacez le nouveau point à la position "
"désirée."
-#: widgets.py:405
+#: widgets.py:382
msgid "Give a name and set category before uploading a file."
msgstr ""
"Renseignez le nom et choisissez au moins une catégorie avant de déposer un "
"fichier."
-#: widgets.py:408
+#: widgets.py:385
msgid "Upload a route file (GPX or KML)"
msgstr "Déposer un trajet (fichier GPX ou KML)"
-#: widgets.py:409
+#: widgets.py:386
msgid "or"
msgstr "ou"
-#: widgets.py:414
+#: widgets.py:391
msgid "Start \"hand\" drawing"
msgstr "Commencer le tracé manuellement"
-#: widgets.py:437
+#: widgets.py:414
msgid "Move on the map"
msgstr "Se déplacer"
-#: widgets.py:437
+#: widgets.py:414
msgid "Draw"
msgstr "Tracer"
-#: widgets.py:527
+#: widgets.py:504
msgid "Hold CTRL, click and drag to select area on the map"
msgstr ""
"Maintenir la touche Control, cliquez puis glissez pour sélectionner une zone "
"sur la carte"
-#: widgets.py:584
+#: widgets.py:561
msgid "Type:"
msgstr "Type :"
-#: widgets.py:584
+#: widgets.py:561
msgid "Node"
msgstr "Nœud"
-#: widgets.py:585
+#: widgets.py:562
msgid "Way"
msgstr "Route"
-#: widgets.py:596
+#: widgets.py:573
msgid ""
"Enter an OSM \"tag=value\" string such as \"amenity=pub\". A list of common "
"tag is available <a href='https://wiki.openstreetmap.org/wiki/Map_Features' "
@@ -1179,39 +1188,39 @@ msgstr ""
"liste des clés est disponible <a href='https://wiki.openstreetmap.org/wiki/"
"FR:Map_Features' target='_blank'>ici</a>."
-#: widgets.py:603
+#: widgets.py:580
msgid "Tag:"
msgstr "Clé/valeur :"
-#: widgets.py:607
+#: widgets.py:584
msgid "You have to select an area."
msgstr "Vous devez sélectionner une zone."
-#: widgets.py:609
+#: widgets.py:586
msgid "You have to select a type."
msgstr "Vous devez sélectionner un type."
-#: widgets.py:611
+#: widgets.py:588
msgid "You have to insert a filter tag."
msgstr "Vous devez saisir une clé=valeur."
-#: widgets.py:613
+#: widgets.py:590
msgid "If you change the above form don't forget to refresh before submit!"
msgstr ""
"Si vous modifiez le formulaire ci-dessus n'oubliez pas de rafraîchir avant "
"de valider !"
-#: widgets.py:616
+#: widgets.py:593
msgid "You can put a Folder name of the KML file to filter on it."
msgstr ""
"Vous pouvez saisir le nom d'un « Folder » du fichier KML pour filter sur "
"celui-ci."
-#: widgets.py:624
+#: widgets.py:601
msgid "Refresh"
msgstr "Rafraîchir"
-#: widgets.py:690
+#: widgets.py:667
msgid "Select..."
msgstr "Sélectionner..."
@@ -1306,7 +1315,7 @@ msgstr ""
"Après ajout/modification de modèle de propriété vous aurez à recharger le "
"serveur web."
-#: templates/chimere/base.html:15
+#: templates/chimere/base.html:12
msgid "You must enable JavaScript in your browser to display Chimère."
msgstr ""
"Vous devez activer le JavaScript dans votre navigateur pour afficher Chimère."
@@ -1322,63 +1331,76 @@ msgstr ""
msgid "Submit"
msgstr "Proposer"
-#: templates/chimere/detail.html:17
+#: templates/chimere/detail.html:16
msgid "Date:"
msgstr "Date :"
-#: templates/chimere/detail.html:26
+#: templates/chimere/detail.html:25
msgid "Source:"
msgstr "Source :"
-#: templates/chimere/detail.html:27
+#: templates/chimere/detail.html:26
msgid "License:"
msgstr "Licence :"
-#: templates/chimere/detail.html:29
+#: templates/chimere/detail.html:28
msgid "Show multimedia gallery"
msgstr "Montrer la galerie multimedia"
+#: templates/chimere/detail.html:33
+msgid "Edit"
+msgstr "Modifier"
+
#: templates/chimere/detail.html:34
msgid "Submit an amendment"
msgstr "Proposer une modification"
-#: templates/chimere/detail.html:37 templates/chimere/detail.html.py:38
+#: templates/chimere/detail.html:38 templates/chimere/detail.html.py:39
msgid "Propose amendment"
msgstr "Proposer une modification"
-#: templates/chimere/detail.html:37
+#: templates/chimere/detail.html:38
msgid "I would like to propose an amendment for this item:"
msgstr "Je souhaiterais proposer une modification pour cet élément :"
-#: templates/chimere/edit.html:20
+#: templates/chimere/edit.html:22
msgid "Error"
msgstr "Erreur"
-#: templates/chimere/edit.html:23 templates/chimere/edit_route.html:20
+#: templates/chimere/edit.html:25
msgid ""
-"You are logged as an administrator. Your modifications will be taking into "
+"You have write rights for this map. Your modifications will be taking into "
"account immediately."
msgstr ""
-"Vous êtes connecté comme administrateur. Vos modifications vont être prises "
-"en compte immédiatement."
+"Vous avez les droits d'écriture sur cette carte. Vos modifications vont être "
+"prises en compte immédiatement."
-#: templates/chimere/edit.html:25
+#: templates/chimere/edit.html:27
msgid "Modify a point of interest"
msgstr "Modifier un point d'intérêt"
-#: templates/chimere/edit.html:25
+#: templates/chimere/edit.html:27
msgid "Add a point of interest"
msgstr "Ajout d'un point d'intérêt"
-#: templates/chimere/edit.html:31
+#: templates/chimere/edit.html:29 templates/chimere/edit.html.py:180
+msgid "Add/modify"
+msgstr "Ajouter - Modifier"
+
+#: templates/chimere/edit.html:29 templates/chimere/edit.html.py:180
+#: templates/chimere/edit_route.html:103
+msgid "Propose"
+msgstr "Proposez"
+
+#: templates/chimere/edit.html:34
msgid "Point"
msgstr "Point"
-#: templates/chimere/edit.html:32 templates/chimere/edit_route.html:29
+#: templates/chimere/edit.html:35 templates/chimere/edit_route.html:31
msgid "Select a location for this new site"
msgstr "Choisissez une localisation pour ce nouveau site"
-#: templates/chimere/edit.html:38 templates/chimere/edit_route.html:35
+#: templates/chimere/edit.html:42 templates/chimere/edit_route.html:37
msgid "indicates a mandatory field"
msgstr "indique un champ obligatoire"
@@ -1411,22 +1433,46 @@ msgstr "Commentaires au sujet de votre proposition"
msgid "Upload in progress. Please wait..."
msgstr "Dépôt en cours. Veuillez patienter..."
-#: templates/chimere/edit.html:159 templates/chimere/edit_route.html:78
-msgid "Propose"
-msgstr "Proposez"
-
#: templates/chimere/edit_route.html:22
+msgid ""
+"You are logged as an administrator. Your modifications will be taking into "
+"account immediately."
+msgstr ""
+"Vous êtes connecté comme administrateur. Vos modifications vont être prises "
+"en compte immédiatement."
+
+#: templates/chimere/edit_route.html:24
msgid "Modify a route"
msgstr "Modifier un trajet"
-#: templates/chimere/edit_route.html:22
+#: templates/chimere/edit_route.html:24
msgid "Add a route"
msgstr "Ajout d'un nouveau trajet"
-#: templates/chimere/main_map.html:38
+#: templates/chimere/main_map.html:22 templates/chimere/no_map.html:21
+msgid "Identified as: "
+msgstr "Identifié en tant que : "
+
+#: templates/chimere/main_map.html:22 templates/chimere/no_map.html:21
+msgid "Logout"
+msgstr "Se déconnecter"
+
+#: templates/chimere/main_map.html:48
msgid "Simple map"
msgstr "Carte simple"
+#: templates/chimere/no_map.html:31
+msgid "No map are currently available for your account."
+msgstr "Aucune carte n'est disponible actuellement pour votre compte."
+
+#: templates/chimere/no_map.html:33
+msgid ""
+"No map are currently available for public access. If you have an account "
+"identify yourself."
+msgstr ""
+"Aucune carte n'est disponible actuellement en accès public. Si vous avez un "
+"compte identifiez vous."
+
#: templates/chimere/upload_file.html:13
msgid "Thank you for your submission!"
msgstr "Merci pour votre proposition !"
@@ -1487,17 +1533,13 @@ msgstr "Vous devez renseigner un fichier ou une adresse web."
msgid "You must provide a web address."
msgstr "Vous devez fournir une adresse web."
-#: templates/chimere/blocks/areas.html:4
-msgid "Maps"
-msgstr "Cartes"
-
-#: templates/chimere/blocks/areas_alternative.html:4
-msgid "Shortcuts"
-msgstr "Raccourcis"
+#: templates/chimere/blocks/areas.html:5 templates/chimere/blocks/maps.html:5
+msgid "Maps:"
+msgstr "Cartes :"
-#: templates/chimere/blocks/areas_alternative.html:7
#: templates/chimere/blocks/categories.html:8
#: templates/chimere/blocks/categories.html:17
+#: templates/chimere/blocks/maps_alternative.html:7
msgid "Zoom to"
msgstr "Zoomer sur"
@@ -1514,23 +1556,19 @@ msgstr ""
msgid "This site uses Chimère"
msgstr "Ce site utilise Chimère"
-#: templates/chimere/blocks/footer.html:2
-msgid "Map"
-msgstr "Carte"
-
-#: templates/chimere/blocks/map.html:15
+#: templates/chimere/blocks/map.html:9
msgid "Loading of the map in progress"
msgstr "Chargement de la carte en cours"
-#: templates/chimere/blocks/map.html:19
+#: templates/chimere/blocks/map.html:13
msgid "Display options"
msgstr "Options d'affichage"
-#: templates/chimere/blocks/map.html:21
+#: templates/chimere/blocks/map.html:15
msgid "Map type"
msgstr "Type de carte"
-#: templates/chimere/blocks/map.html:32
+#: templates/chimere/blocks/map.html:25
msgid "Permalink"
msgstr "Lien permanent"
@@ -1566,6 +1604,10 @@ msgstr "Zoomer en arrière"
msgid "Center the map here"
msgstr "Centrer la carte ici"
+#: templates/chimere/blocks/maps_alternative.html:4
+msgid "Shortcuts"
+msgstr "Raccourcis"
+
#: templates/chimere/blocks/multimedia_file.html:19
msgid "Please use a modern browser or install the non free Flash-Plugin."
msgstr ""
@@ -1577,6 +1619,14 @@ msgstr ""
msgid "See it on the map"
msgstr "Voir sur la carte"
+#: templates/chimere/blocks/nominatim_widget.html:5
+msgid "Search:"
+msgstr "Rechercher :"
+
+#: templates/chimere/blocks/nominatim_widget.html:10
+msgid "Street, City, Country"
+msgstr "Rue, Commune, Pays"
+
#: templates/chimere/blocks/routing.html:5
msgid "Itinerary"
msgstr "Itinéraire"
@@ -1585,7 +1635,7 @@ msgstr "Itinéraire"
msgid "Add a step"
msgstr "Ajouter une étape"
-#: templates/chimere/blocks/routing.html:17 templates/search/search.html:33
+#: templates/chimere/blocks/routing.html:17
msgid "Search"
msgstr "Rechercher"
@@ -1617,27 +1667,43 @@ msgstr "Partager sur"
msgid "Share"
msgstr "Partager"
-#: templates/chimere/blocks/submited.html:3
+#: templates/chimere/blocks/submited.html:6
+msgid "Your modification has been taken into account."
+msgstr "Votre modification a été prise en compte."
+
+#: templates/chimere/blocks/submited.html:8
msgid ""
-"Your new proposition/modification has been submited. A moderator will treat "
-"your submission shortly. Thanks!"
+"Your modification has been submited. A moderator will treat your submission "
+"shortly. Thanks!"
msgstr ""
-"Votre proposition/modification a été soumise. Un modérateur va traiter votre "
+"Votre proposition a été soumise. Un modérateur va traiter votre "
"proposition sous peu. Merci !"
-#: templates/chimere/blocks/submited.html:8
+#: templates/chimere/blocks/submited.html:12
+msgid "Your new proposition has been added."
+msgstr "Votre nouvelle proposition a été ajoutée."
+
+#: templates/chimere/blocks/submited.html:14
+msgid ""
+"Your new proposition has been submited. A moderator will treat your "
+"submission shortly. Thanks!"
+msgstr ""
+"Votre proposition a été soumise. Un modérateur va traiter votre "
+"proposition sous peu. Merci !"
+
+#: templates/chimere/blocks/submited.html:21
msgid "Thank you"
msgstr "Merci"
-#: templates/chimere/blocks/submited.html:12
+#: templates/chimere/blocks/submited.html:25
msgid "Add a new item"
msgstr "Ajout d'un nouvel élément"
-#: templates/chimere/blocks/submited.html:16
+#: templates/chimere/blocks/submited.html:29
msgid "Continue edition of this item"
msgstr "Continuer l'édition de cet élément"
-#: templates/chimere/blocks/submited.html:20
+#: templates/chimere/blocks/submited.html:33
msgid "Return to the map"
msgstr "Retourner à la carte"
@@ -1670,8 +1736,8 @@ msgid "Choose a category"
msgstr "Choisir une catégorie"
#: templates/chimere/feeds/rss.html:51
-msgid "Choose a pre-defined areas"
-msgstr "Choisir une zone pré-définie"
+msgid "Choose a pre-defined map"
+msgstr "Choisir une carte pré-définie"
#: templates/chimere/feeds/rss.html:65
msgid "Or select the area by zooming and panning this map"
@@ -1685,35 +1751,17 @@ msgstr "Description :"
msgid ":"
msgstr " :"
-#: templates/search/search.html:3
-msgid "Do you mean: "
-msgstr "Voulez-vous dire :"
-
-#: templates/search/search.html:4
-msgid "?"
-msgstr " ?"
-
-#: templates/search/search.html:15
-msgid "No results found."
-msgstr "Pas de résultats trouvés."
-
-#: templates/search/search.html:23
-msgid "Previous"
-msgstr "Précédent"
-
-#: templates/search/search.html:24
-msgid "More results..."
-msgstr "Plus de résultats..."
-
-#: templates/search/search.html:38
-msgid "No exact match."
-msgstr "Pas de correspondance exacte."
-
-#: templatetags/chimere_tags.py:93
+#: templatetags/chimere_tags.py:72
#, python-format
msgid "Welcome to the %s"
msgstr "Bienvenue sur %s"
+#~ msgid "Invalid CSV format"
+#~ msgstr "Fichier CSV non valide"
+
+#~ msgid "Associated areas"
+#~ msgstr "Zones associées"
+
#~ msgid "Areas:"
#~ msgstr "Zones :"
@@ -1729,9 +1777,6 @@ msgstr "Bienvenue sur %s"
#~ msgid "Administration de Chimère"
#~ msgstr "Administration de Chimère"
-#~ msgid "Add/modify a site"
-#~ msgstr "Ajouter ou modifier un site"
-
#~ msgid "Categorys"
#~ msgstr "Catégories"
diff --git a/chimere/migrations/0009_auto__del_arealayers__del_area__add_map__add_mapgroups__add_mapusers__.py b/chimere/migrations/0009_auto__del_arealayers__del_area__add_map__add_mapgroups__add_mapusers__.py
new file mode 100644
index 0000000..3f018ee
--- /dev/null
+++ b/chimere/migrations/0009_auto__del_arealayers__del_area__add_map__add_mapgroups__add_mapusers__.py
@@ -0,0 +1,503 @@
+# -*- coding: utf-8 -*-
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+
+class Migration(SchemaMigration):
+
+ def forwards(self, orm):
+ # Renaming model 'Area'
+ db.rename_table('chimere_area', 'chimere_map')
+
+ # Renaming model 'AreaLayers'
+ db.rename_table('chimere_arealayers', 'chimere_maplayers')
+ db.rename_column('chimere_maplayers', 'area_id', 'map_id')
+
+ # Renaming M2M table for field subcategories on 'Area'
+ db.rename_table('chimere_subcategory_areas', 'chimere_subcategory_maps')
+ db.rename_column('chimere_subcategory_maps', 'area_id', 'map_id')
+
+ # Renaming M2M table for field default_subcategories on 'Area'
+ db.rename_table('chimere_area_default_subcategories',
+ 'chimere_map_default_subcategories')
+ db.rename_column('chimere_map_default_subcategories', 'area_id',
+ 'map_id')
+
+ # Renaming M2M table for field areas on 'News'
+ db.rename_table('chimere_news_areas', 'chimere_news_maps')
+ db.rename_column('chimere_news_maps', 'area_id', 'map_id')
+
+ # Adding field 'Map.public_read'
+ db.add_column('chimere_map', 'public_read',
+ self.gf('django.db.models.fields.BooleanField')(default=False),
+ keep_default=False)
+
+ # Adding field 'Map.public_propose'
+ db.add_column('chimere_map', 'public_propose',
+ self.gf('django.db.models.fields.BooleanField')(default=False),
+ keep_default=False)
+
+ # Adding field 'Map.public_write'
+ db.add_column('chimere_map', 'public_write',
+ self.gf('django.db.models.fields.BooleanField')(default=False),
+ keep_default=False)
+
+ # Adding field 'Map.cluster'
+ db.add_column('chimere_map', 'cluster',
+ self.gf('django.db.models.fields.BooleanField')(default=False),
+ keep_default=False)
+
+ # Changing field 'Map.default'
+ db.execute('update chimere_map set "default"=False where "default" is NULL')
+ db.alter_column('chimere_map', 'default', self.gf('django.db.models.fields.BooleanField')())
+
+ # Changing field 'MapLayers.default'
+ db.execute('update chimere_maplayers set "default"=False where "default" is NULL')
+ db.alter_column('chimere_maplayers', 'default', self.gf('django.db.models.fields.BooleanField')())
+
+ # Adding model 'MapGroups'
+ db.create_table('chimere_mapgroups', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('map', self.gf('django.db.models.fields.related.ForeignKey')(related_name='mapgroups', to=orm['chimere.Map'])),
+ ('group', self.gf('django.db.models.fields.related.ForeignKey')(related_name='mapgroups', to=orm['auth.Group'])),
+ ('read', self.gf('django.db.models.fields.BooleanField')(default=False)),
+ ('propose', self.gf('django.db.models.fields.BooleanField')(default=False)),
+ ('write', self.gf('django.db.models.fields.BooleanField')(default=False)),
+ ))
+ db.send_create_signal('chimere', ['MapGroups'])
+
+ # Adding model 'MapUsers'
+ db.create_table('chimere_mapusers', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('map', self.gf('django.db.models.fields.related.ForeignKey')(related_name='mapusers', to=orm['chimere.Map'])),
+ ('user', self.gf('django.db.models.fields.related.ForeignKey')(related_name='mapusers', to=orm['auth.User'])),
+ ('read', self.gf('django.db.models.fields.BooleanField')(default=False)),
+ ('propose', self.gf('django.db.models.fields.BooleanField')(default=False)),
+ ('write', self.gf('django.db.models.fields.BooleanField')(default=False)),
+ ))
+ db.send_create_signal('chimere', ['MapUsers'])
+
+ # Adding field 'PropertyModel.slug'
+ db.add_column('chimere_propertymodel', 'slug',
+ self.gf('django.db.models.fields.SlugField')(null=True, blank=True, max_length=50))
+ for pm in db.execute("select id, name from chimere_propertymodel"):
+ id, name = pm
+ db.execute("update chimere_propertymodel set slug='%s' where id=%d" % (defaultfilters.slugify(name), id))
+ db.execute("alter table chimere_propertymodel alter column slug set not null")
+
+ # Adding field 'SubCategory.slug'
+ db.add_column('chimere_subcategory', 'slug',
+ self.gf('django.db.models.fields.SlugField')(max_length=50, null=True, blank=True),
+ keep_default=False)
+ for cat in orm.Subcategory.objects.all():
+ cat.slug = defaultfilters.slugify(cat.name)
+ cat.save()
+ db.alter_column('chimere_subcategory', 'slug', self.gf('django.db.models.fields.SlugField')(default='', max_length=50))
+
+ # Adding field 'Category.slug'
+ db.add_column('chimere_category', 'slug',
+ self.gf('django.db.models.fields.SlugField')(max_length=50, null=True, blank=True),
+ keep_default=False)
+ for cat in orm.Category.objects.all():
+ cat.slug = defaultfilters.slugify(cat.name)
+ cat.save()
+ db.alter_column('chimere_category', 'slug', self.gf('django.db.models.fields.SlugField')(default='', max_length=50))
+
+ # Adding field 'Marker.weight'
+ db.add_column('chimere_marker', 'weight',
+ self.gf('django.db.models.fields.IntegerField')(default=0, null=True, blank=True),
+ keep_default=False)
+
+ # Adding field 'SubCategory.weighted'
+ db.add_column('chimere_subcategory', 'weighted',
+ self.gf('django.db.models.fields.BooleanField')(default=False),
+ keep_default=False)
+
+
+ def backwards(self, orm):
+ # Changing field 'Map.default'
+ db.alter_column('chimere_map', 'default', self.gf('django.db.models.fields.NullBooleanField')(null=True))
+
+ # Changing field 'MapLayers.default'
+ db.alter_column('chimere_maplayers', 'default', self.gf('django.db.models.fields.NullBooleanField')(null=True))
+
+ db.rename_table('chimere_map', 'chimere_area')
+ db.rename_table('chimere_maplayers', 'chimere_arealayers')
+ db.rename_column('chimere_arealayers', 'map_id', 'area_id')
+ db.rename_table('chimere_subcategory_maps', 'chimere_subcategory_areas')
+ db.rename_column('chimere_subcategory_areas', 'map_id', 'area_id')
+ db.rename_table('chimere_map_default_subcategories',
+ 'chimere_area_default_subcategories')
+ db.rename_column('chimere_area_default_subcategories', 'map_id',
+ 'area_id')
+ db.rename_table('chimere_news_maps',
+ 'chimere_news_areas ')
+ db.rename_column('chimere_news_maps', 'map_id', 'area_id')
+
+ # Deleting field 'Map.public_read'
+ db.delete_column('chimere_map', 'public_read')
+
+ # Deleting field 'Map.public_propose'
+ db.delete_column('chimere_map', 'public_propose')
+
+ # Deleting field 'Map.public_write'
+ db.delete_column('chimere_map', 'public_write')
+
+ # Deleting field 'Map.cluster'
+ db.delete_column('chimere_map', 'cluster')
+
+ # Deleting model 'MapGroups'
+ db.delete_table('chimere_mapgroups')
+
+ # Deleting model 'MapUsers'
+ db.delete_table('chimere_mapusers')
+
+ # Deleting field 'Marker.weight'
+ db.delete_column('chimere_marker', 'weight')
+
+ # Deleting field 'SubCategory.slug'
+ db.delete_column('chimere_subcategory', 'slug')
+
+ # Deleting field 'SubCategory.weighted'
+ db.delete_column('chimere_subcategory', 'weighted')
+
+ # Deleting field 'PropertyModel.slug'
+ db.delete_column('chimere_propertymodel', 'slug')
+
+ # Deleting field 'Category.slug'
+ db.delete_column('chimere_category', 'slug')
+
+
+ models = {
+ 'auth.group': {
+ 'Meta': {'object_name': 'Group'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+ 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+ },
+ 'auth.permission': {
+ 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
+ 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+ },
+ 'auth.user': {
+ 'Meta': {'object_name': 'User'},
+ 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+ 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+ 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
+ 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+ },
+ 'chimere.aggregatedroute': {
+ 'Meta': {'object_name': 'AggregatedRoute', 'db_table': "'chimere_aggregated_routes'", 'managed': 'False'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'route': ('django.contrib.gis.db.models.fields.MultiLineStringField', [], {}),
+ 'status': ('django.db.models.fields.CharField', [], {'max_length': '1'}),
+ 'subcategory': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['chimere.SubCategory']"})
+ },
+ 'chimere.category': {
+ 'Meta': {'ordering': "['order']", 'object_name': 'Category'},
+ 'available': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '150'}),
+ 'order': ('django.db.models.fields.IntegerField', [], {}),
+ 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50'})
+ },
+ 'chimere.color': {
+ 'Meta': {'ordering': "['order']", 'object_name': 'Color'},
+ 'code': ('django.db.models.fields.CharField', [], {'max_length': '6'}),
+ 'color_theme': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'colors'", 'to': "orm['chimere.ColorTheme']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'order': ('django.db.models.fields.IntegerField', [], {})
+ },
+ 'chimere.colortheme': {
+ 'Meta': {'object_name': 'ColorTheme'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '150'})
+ },
+ 'chimere.icon': {
+ 'Meta': {'object_name': 'Icon'},
+ 'height': ('django.db.models.fields.IntegerField', [], {}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'image': ('django.db.models.fields.files.ImageField', [], {'max_length': '100'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '150'}),
+ 'width': ('django.db.models.fields.IntegerField', [], {})
+ },
+ 'chimere.importer': {
+ 'Meta': {'object_name': 'Importer'},
+ 'associate_marker_to_way': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'automatic_update': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'categories': ('chimere.widgets.SelectMultipleField', [], {'symmetrical': 'False', 'to': "orm['chimere.SubCategory']", 'null': 'True', 'blank': 'True'}),
+ 'default_description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+ 'default_localisation': ('chimere.widgets.PointField', [], {'null': 'True', 'blank': 'True'}),
+ 'default_name': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
+ 'filtr': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
+ 'get_description': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'importer_type': ('django.db.models.fields.CharField', [], {'max_length': '4'}),
+ 'license': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
+ 'origin': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
+ 'overwrite': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'source': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
+ 'source_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
+ 'source_file_alt': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
+ 'srid': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
+ 'state': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+ 'zipped': ('django.db.models.fields.BooleanField', [], {'default': 'False'})
+ },
+ 'chimere.importerkeycategories': {
+ 'Meta': {'object_name': 'ImporterKeyCategories'},
+ 'category': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['chimere.SubCategory']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'importer': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'key_categories'", 'to': "orm['chimere.Importer']"}),
+ 'key': ('django.db.models.fields.CharField', [], {'max_length': '200'})
+ },
+ 'chimere.layer': {
+ 'Meta': {'object_name': 'Layer'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'layer_code': ('django.db.models.fields.TextField', [], {'max_length': '300'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '150'})
+ },
+ 'chimere.map': {
+ 'Meta': {'ordering': "('order', 'name')", 'object_name': 'Map'},
+ 'available': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'cluster': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'default': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'default_subcategories': ('chimere.widgets.SelectMultipleField', [], {'to': "orm['chimere.SubCategory']", 'symmetrical': 'False', 'blank': 'True'}),
+ 'dynamic_categories': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
+ 'external_css': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'layers': ('chimere.widgets.SelectMultipleField', [], {'symmetrical': 'False', 'related_name': "'maps'", 'blank': 'True', 'through': "orm['chimere.MapLayers']", 'to': "orm['chimere.Layer']"}),
+ 'lower_right_corner': ('django.contrib.gis.db.models.fields.PointField', [], {'default': "'POINT(0 0)'"}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '150'}),
+ 'order': ('django.db.models.fields.IntegerField', [], {'unique': 'True'}),
+ 'public_propose': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'public_read': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'public_write': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'restrict_to_extent': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'subcategories': ('chimere.widgets.SelectMultipleField', [], {'symmetrical': 'False', 'related_name': "'maps'", 'blank': 'True', 'db_table': "'chimere_subcategory_maps'", 'to': "orm['chimere.SubCategory']"}),
+ 'upper_left_corner': ('django.contrib.gis.db.models.fields.PointField', [], {'default': "'POINT(0 0)'"}),
+ 'urn': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '50', 'blank': 'True'}),
+ 'users': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.User']", 'through': "orm['chimere.MapUsers']", 'symmetrical': 'False'}),
+ 'welcome_message': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'})
+ },
+ 'chimere.mapgroups': {
+ 'Meta': {'object_name': 'MapGroups'},
+ 'group': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'mapgroups'", 'to': "orm['auth.Group']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'map': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'mapgroups'", 'to': "orm['chimere.Map']"}),
+ 'propose': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'read': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'write': ('django.db.models.fields.BooleanField', [], {'default': 'False'})
+ },
+ 'chimere.maplayers': {
+ 'Meta': {'ordering': "('order',)", 'object_name': 'MapLayers'},
+ 'default': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'layer': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['chimere.Layer']"}),
+ 'map': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['chimere.Map']"}),
+ 'order': ('django.db.models.fields.IntegerField', [], {})
+ },
+ 'chimere.mapusers': {
+ 'Meta': {'object_name': 'MapUsers'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'map': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'mapusers'", 'to': "orm['chimere.Map']"}),
+ 'propose': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'read': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'mapusers'", 'to': "orm['auth.User']"}),
+ 'write': ('django.db.models.fields.BooleanField', [], {'default': 'False'})
+ },
+ 'chimere.marker': {
+ 'Meta': {'ordering': "('status', 'name')", 'object_name': 'Marker'},
+ 'available_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+ 'categories': ('chimere.widgets.SelectMultipleField', [], {'to': "orm['chimere.SubCategory']", 'symmetrical': 'False'}),
+ 'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+ 'end_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'import_key': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
+ 'import_source': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
+ 'import_version': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
+ 'is_front_page': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
+ 'license': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
+ 'modified_since_import': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '150'}),
+ 'not_for_osm': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'origin': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
+ 'point': ('chimere.widgets.PointField', [], {}),
+ 'ref_item': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'submited_marker'", 'null': 'True', 'to': "orm['chimere.Marker']"}),
+ 'route': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'associated_marker'", 'null': 'True', 'to': "orm['chimere.Route']"}),
+ 'start_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+ 'status': ('django.db.models.fields.CharField', [], {'max_length': '1'}),
+ 'submiter_comment': ('django.db.models.fields.TextField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
+ 'submiter_email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'null': 'True', 'blank': 'True'}),
+ 'submiter_name': ('django.db.models.fields.CharField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'}),
+ 'submiter_session_key': ('django.db.models.fields.CharField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'}),
+ 'weight': ('django.db.models.fields.IntegerField', [], {'default': '0', 'null': 'True', 'blank': 'True'})
+ },
+ 'chimere.multimediaextension': {
+ 'Meta': {'object_name': 'MultimediaExtension'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'multimedia_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'extensions'", 'to': "orm['chimere.MultimediaType']"}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '6'})
+ },
+ 'chimere.multimediafile': {
+ 'Meta': {'object_name': 'MultimediaFile'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'marker': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'multimedia_files'", 'to': "orm['chimere.Marker']"}),
+ 'miniature': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'multimedia_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['chimere.MultimediaType']", 'null': 'True', 'blank': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '150'}),
+ 'order': ('django.db.models.fields.IntegerField', [], {'default': '1'}),
+ 'url': ('django.db.models.fields.URLField', [], {'max_length': '200'})
+ },
+ 'chimere.multimediatype': {
+ 'Meta': {'object_name': 'MultimediaType'},
+ 'available': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'iframe': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'media_type': ('django.db.models.fields.CharField', [], {'max_length': '1'}),
+ 'mime_type': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '150'})
+ },
+ 'chimere.news': {
+ 'Meta': {'object_name': 'News'},
+ 'available': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'content': ('django.db.models.fields.TextField', [], {}),
+ 'date': ('django.db.models.fields.DateField', [], {}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'is_front_page': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
+ 'maps': ('chimere.widgets.SelectMultipleField', [], {'symmetrical': 'False', 'to': "orm['chimere.Map']", 'null': 'True', 'blank': 'True'}),
+ 'title': ('django.db.models.fields.CharField', [], {'max_length': '150'}),
+ 'url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'})
+ },
+ 'chimere.page': {
+ 'Meta': {'object_name': 'Page'},
+ 'available': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'content': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'mnemonic': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True', 'blank': 'True'}),
+ 'order': ('django.db.models.fields.IntegerField', [], {'default': '10', 'null': 'True', 'blank': 'True'}),
+ 'template_path': ('django.db.models.fields.CharField', [], {'max_length': '150', 'null': 'True', 'blank': 'True'}),
+ 'title': ('django.db.models.fields.CharField', [], {'max_length': '150'})
+ },
+ 'chimere.picturefile': {
+ 'Meta': {'object_name': 'PictureFile'},
+ 'height': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'marker': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'pictures'", 'to': "orm['chimere.Marker']"}),
+ 'miniature': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '150'}),
+ 'order': ('django.db.models.fields.IntegerField', [], {'default': '1'}),
+ 'picture': ('django.db.models.fields.files.ImageField', [], {'max_length': '100'}),
+ 'thumbnailfile': ('django.db.models.fields.files.ImageField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
+ 'thumbnailfile_height': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
+ 'thumbnailfile_width': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
+ 'width': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'})
+ },
+ 'chimere.property': {
+ 'Meta': {'object_name': 'Property'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'marker': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['chimere.Marker']"}),
+ 'propertymodel': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['chimere.PropertyModel']"}),
+ 'value': ('django.db.models.fields.TextField', [], {})
+ },
+ 'chimere.propertymodel': {
+ 'Meta': {'ordering': "('order',)", 'object_name': 'PropertyModel'},
+ 'available': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'mandatory': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '150'}),
+ 'order': ('django.db.models.fields.IntegerField', [], {}),
+ 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50'}),
+ 'subcategories': ('chimere.widgets.SelectMultipleField', [], {'symmetrical': 'False', 'related_name': "'properties'", 'blank': 'True', 'to': "orm['chimere.SubCategory']"}),
+ 'type': ('django.db.models.fields.CharField', [], {'max_length': '1'})
+ },
+ 'chimere.propertymodelchoice': {
+ 'Meta': {'object_name': 'PropertyModelChoice'},
+ 'available': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'propertymodel': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'choices'", 'to': "orm['chimere.PropertyModel']"}),
+ 'value': ('django.db.models.fields.CharField', [], {'max_length': '150'})
+ },
+ 'chimere.route': {
+ 'Meta': {'ordering': "('status', 'name')", 'object_name': 'Route'},
+ 'associated_file': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['chimere.RouteFile']", 'null': 'True', 'blank': 'True'}),
+ 'categories': ('chimere.widgets.SelectMultipleField', [], {'to': "orm['chimere.SubCategory']", 'symmetrical': 'False'}),
+ 'end_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+ 'has_associated_marker': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'height': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'import_key': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
+ 'import_source': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
+ 'import_version': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
+ 'license': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
+ 'modified_since_import': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '150'}),
+ 'not_for_osm': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'origin': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
+ 'picture': ('django.db.models.fields.files.ImageField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
+ 'ref_item': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'submited_route'", 'null': 'True', 'to': "orm['chimere.Route']"}),
+ 'route': ('chimere.widgets.RouteField', [], {}),
+ 'start_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+ 'status': ('django.db.models.fields.CharField', [], {'max_length': '1'}),
+ 'submiter_comment': ('django.db.models.fields.TextField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
+ 'submiter_email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'null': 'True', 'blank': 'True'}),
+ 'submiter_name': ('django.db.models.fields.CharField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'}),
+ 'submiter_session_key': ('django.db.models.fields.CharField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'}),
+ 'width': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'})
+ },
+ 'chimere.routefile': {
+ 'Meta': {'ordering': "('name',)", 'object_name': 'RouteFile'},
+ 'file_type': ('django.db.models.fields.CharField', [], {'max_length': '1'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '150'}),
+ 'raw_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}),
+ 'simplified_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'})
+ },
+ 'chimere.subcategory': {
+ 'Meta': {'ordering': "['category', 'order']", 'object_name': 'SubCategory'},
+ 'as_layer': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'available': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'category': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'subcategories'", 'to': "orm['chimere.Category']"}),
+ 'color_theme': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['chimere.ColorTheme']", 'null': 'True', 'blank': 'True'}),
+ 'dated': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+ 'hover_icon': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'subcat_hovered'", 'null': 'True', 'to': "orm['chimere.Icon']"}),
+ 'icon': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['chimere.Icon']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'item_type': ('django.db.models.fields.CharField', [], {'max_length': '1'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '150'}),
+ 'order': ('django.db.models.fields.IntegerField', [], {'default': '1000'}),
+ 'routing_warn': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50'}),
+ 'submission': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'weighted': ('django.db.models.fields.BooleanField', [], {'default': 'False'})
+ },
+ 'chimere.tinyurl': {
+ 'Meta': {'object_name': 'TinyUrl'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'parameters': ('django.db.models.fields.CharField', [], {'max_length': '500'})
+ },
+ 'contenttypes.contenttype': {
+ 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+ 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+ }
+ }
+
+ complete_apps = ['chimere']
diff --git a/chimere/migrations/0009_auto__add_field_marker_keywords__add_field_route_keywords.py b/chimere/migrations/0010_auto__add_field_marker_keywords__add_field_route_keywords.py
index daeb1be..779ab51 100644
--- a/chimere/migrations/0009_auto__add_field_marker_keywords__add_field_route_keywords.py
+++ b/chimere/migrations/0010_auto__add_field_marker_keywords__add_field_route_keywords.py
@@ -28,6 +28,35 @@ class Migration(SchemaMigration):
models = {
+ 'auth.group': {
+ 'Meta': {'object_name': 'Group'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+ 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+ },
+ 'auth.permission': {
+ 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
+ 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+ },
+ 'auth.user': {
+ 'Meta': {'object_name': 'User'},
+ 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+ 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+ 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
+ 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+ },
'chimere.aggregatedroute': {
'Meta': {'object_name': 'AggregatedRoute', 'db_table': "'chimere_aggregated_routes'", 'managed': 'False'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
@@ -35,44 +64,19 @@ class Migration(SchemaMigration):
'status': ('django.db.models.fields.CharField', [], {'max_length': '1'}),
'subcategory': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['chimere.SubCategory']"})
},
- 'chimere.area': {
- 'Meta': {'ordering': "('order', 'name')", 'object_name': 'Area'},
- 'available': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
- 'default': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
- 'default_subcategories': ('chimere.widgets.SelectMultipleField', [], {'to': "orm['chimere.SubCategory']", 'symmetrical': 'False', 'blank': 'True'}),
- 'dynamic_categories': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
- 'external_css': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'layers': ('chimere.widgets.SelectMultipleField', [], {'symmetrical': 'False', 'related_name': "'areas'", 'blank': 'True', 'through': "orm['chimere.AreaLayers']", 'to': "orm['chimere.Layer']"}),
- 'lower_right_corner': ('django.contrib.gis.db.models.fields.PointField', [], {'default': "'POINT(0 0)'"}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '150'}),
- 'order': ('django.db.models.fields.IntegerField', [], {'unique': 'True'}),
- 'restrict_to_extent': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
- 'subcategories': ('chimere.widgets.SelectMultipleField', [], {'symmetrical': 'False', 'related_name': "'areas'", 'blank': 'True', 'db_table': "'chimere_subcategory_areas'", 'to': "orm['chimere.SubCategory']"}),
- 'upper_left_corner': ('django.contrib.gis.db.models.fields.PointField', [], {'default': "'POINT(0 0)'"}),
- 'urn': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '50', 'blank': 'True'}),
- 'welcome_message': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'})
- },
- 'chimere.arealayers': {
- 'Meta': {'ordering': "('order',)", 'object_name': 'AreaLayers'},
- 'area': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['chimere.Area']"}),
- 'default': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'layer': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['chimere.Layer']"}),
- 'order': ('django.db.models.fields.IntegerField', [], {})
- },
'chimere.category': {
'Meta': {'ordering': "['order']", 'object_name': 'Category'},
'available': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '150'}),
- 'order': ('django.db.models.fields.IntegerField', [], {})
+ 'order': ('django.db.models.fields.IntegerField', [], {}),
+ 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50'})
},
'chimere.color': {
'Meta': {'ordering': "['order']", 'object_name': 'Color'},
'code': ('django.db.models.fields.CharField', [], {'max_length': '6'}),
- 'color_theme': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['chimere.ColorTheme']"}),
+ 'color_theme': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'colors'", 'to': "orm['chimere.ColorTheme']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'order': ('django.db.models.fields.IntegerField', [], {})
},
@@ -124,6 +128,55 @@ class Migration(SchemaMigration):
'layer_code': ('django.db.models.fields.TextField', [], {'max_length': '300'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '150'})
},
+ 'chimere.map': {
+ 'Meta': {'ordering': "('order', 'name')", 'object_name': 'Map'},
+ 'available': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'cluster': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'default': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'default_subcategories': ('chimere.widgets.SelectMultipleField', [], {'to': "orm['chimere.SubCategory']", 'symmetrical': 'False', 'blank': 'True'}),
+ 'dynamic_categories': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
+ 'external_css': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'layers': ('chimere.widgets.SelectMultipleField', [], {'symmetrical': 'False', 'related_name': "'maps'", 'blank': 'True', 'through': "orm['chimere.MapLayers']", 'to': "orm['chimere.Layer']"}),
+ 'lower_right_corner': ('django.contrib.gis.db.models.fields.PointField', [], {'default': "'POINT(0 0)'"}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '150'}),
+ 'order': ('django.db.models.fields.IntegerField', [], {'unique': 'True'}),
+ 'public_propose': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'public_read': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'public_write': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'restrict_to_extent': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'subcategories': ('chimere.widgets.SelectMultipleField', [], {'symmetrical': 'False', 'related_name': "'maps'", 'blank': 'True', 'db_table': "'chimere_subcategory_maps'", 'to': "orm['chimere.SubCategory']"}),
+ 'upper_left_corner': ('django.contrib.gis.db.models.fields.PointField', [], {'default': "'POINT(0 0)'"}),
+ 'urn': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '50', 'blank': 'True'}),
+ 'users': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.User']", 'through': "orm['chimere.MapUsers']", 'symmetrical': 'False'}),
+ 'welcome_message': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'})
+ },
+ 'chimere.mapgroups': {
+ 'Meta': {'object_name': 'MapGroups'},
+ 'group': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'mapgroups'", 'to': "orm['auth.Group']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'map': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'mapgroups'", 'to': "orm['chimere.Map']"}),
+ 'propose': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'read': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'write': ('django.db.models.fields.BooleanField', [], {'default': 'False'})
+ },
+ 'chimere.maplayers': {
+ 'Meta': {'ordering': "('order',)", 'object_name': 'MapLayers'},
+ 'default': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'layer': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['chimere.Layer']"}),
+ 'map': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['chimere.Map']"}),
+ 'order': ('django.db.models.fields.IntegerField', [], {})
+ },
+ 'chimere.mapusers': {
+ 'Meta': {'object_name': 'MapUsers'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'map': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'mapusers'", 'to': "orm['chimere.Map']"}),
+ 'propose': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'read': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'mapusers'", 'to': "orm['auth.User']"}),
+ 'write': ('django.db.models.fields.BooleanField', [], {'default': 'False'})
+ },
'chimere.marker': {
'Meta': {'ordering': "('status', 'name')", 'object_name': 'Marker'},
'available_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
@@ -149,7 +202,8 @@ class Migration(SchemaMigration):
'submiter_comment': ('django.db.models.fields.TextField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
'submiter_email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'null': 'True', 'blank': 'True'}),
'submiter_name': ('django.db.models.fields.CharField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'}),
- 'submiter_session_key': ('django.db.models.fields.CharField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'})
+ 'submiter_session_key': ('django.db.models.fields.CharField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'}),
+ 'weight': ('django.db.models.fields.IntegerField', [], {'default': '0', 'null': 'True', 'blank': 'True'})
},
'chimere.multimediaextension': {
'Meta': {'object_name': 'MultimediaExtension'},
@@ -178,12 +232,12 @@ class Migration(SchemaMigration):
},
'chimere.news': {
'Meta': {'object_name': 'News'},
- 'areas': ('chimere.widgets.SelectMultipleField', [], {'symmetrical': 'False', 'to': "orm['chimere.Area']", 'null': 'True', 'blank': 'True'}),
'available': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'content': ('django.db.models.fields.TextField', [], {}),
'date': ('django.db.models.fields.DateField', [], {}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_front_page': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
+ 'maps': ('chimere.widgets.SelectMultipleField', [], {'symmetrical': 'False', 'to': "orm['chimere.Map']", 'null': 'True', 'blank': 'True'}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '150'}),
'url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'})
},
@@ -225,6 +279,7 @@ class Migration(SchemaMigration):
'mandatory': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '150'}),
'order': ('django.db.models.fields.IntegerField', [], {}),
+ 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50'}),
'subcategories': ('chimere.widgets.SelectMultipleField', [], {'symmetrical': 'False', 'related_name': "'properties'", 'blank': 'True', 'to': "orm['chimere.SubCategory']"}),
'type': ('django.db.models.fields.CharField', [], {'max_length': '1'})
},
@@ -286,12 +341,21 @@ class Migration(SchemaMigration):
'name': ('django.db.models.fields.CharField', [], {'max_length': '150'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '1000'}),
'routing_warn': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
- 'submission': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
+ 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50'}),
+ 'submission': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'weighted': ('django.db.models.fields.BooleanField', [], {'default': 'False'})
},
'chimere.tinyurl': {
'Meta': {'object_name': 'TinyUrl'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'parameters': ('django.db.models.fields.CharField', [], {'max_length': '500'})
+ },
+ 'contenttypes.contenttype': {
+ 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+ 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
}
}
diff --git a/chimere/models.py b/chimere/models.py
index 77fdaf2..15d264b 100644
--- a/chimere/models.py
+++ b/chimere/models.py
@@ -106,7 +106,7 @@ class News(models.Model):
date = models.DateField(_(u"Date"))
content = models.TextField()
url = models.URLField(_(u"Url"), max_length=200, blank=True, null=True)
- areas = SelectMultipleField('Area', verbose_name=_(u"Associated areas"),
+ maps = SelectMultipleField('Map', verbose_name=_(u"Associated maps"),
blank=True, null=True)
def __unicode__(self):
ordering = ["-date"]
@@ -172,7 +172,8 @@ class Color(models.Model):
"""
code = models.CharField(_(u"Code"), max_length=6)
order = models.IntegerField(_(u"Order"))
- color_theme = models.ForeignKey(ColorTheme, verbose_name=_(u"Color theme"))
+ color_theme = models.ForeignKey(ColorTheme, verbose_name=_(u"Color theme"),
+ related_name='colors')
def __unicode__(self):
return self.code
class Meta:
@@ -183,6 +184,7 @@ class Category(models.Model):
"""Category of Point Of Interest (POI)
"""
name = models.CharField(_(u"Name"), max_length=150)
+ slug = models.SlugField()
available = models.BooleanField(_(u"Available"))
order = models.IntegerField(_(u"Order"))
description = models.TextField(blank=True, null=True)
@@ -211,9 +213,12 @@ class SubCategory(models.Model):
category = models.ForeignKey(Category, verbose_name=_(u"Category"),
related_name='subcategories')
name = models.CharField(_(u"Name"), max_length=150)
+ slug = models.SlugField()
available = models.BooleanField(_(u"Available"), default=True)
submission = models.BooleanField(_(u"Available for submission"),
default=True)
+ weighted = models.BooleanField(_(u"Has an associated quantity"),
+ default=False)
TYPE = (('M', _(u'Marker')),
('R', _(u'Route')),
('B', _(u'Both')),)
@@ -237,7 +242,7 @@ class SubCategory(models.Model):
verbose_name_plural = _(u"Sub-categories")
@classmethod
- def getAvailable(cls, item_types=None, area_name=None, public=False):
+ def getAvailable(cls, item_types=None, map_name=None, public=False):
'''Get list of tuples with first the category and second the associated
subcategories
'''
@@ -250,14 +255,14 @@ class SubCategory(models.Model):
if public:
subcategories = subcategories.filter(submission=True)
selected_cats = []
- if area_name:
- area = Area.objects.get(urn=area_name)
+ if map_name:
+ map = Map.objects.get(urn=map_name)
# if there some restrictions with categories limit them
- if area.subcategories.count():
- sub_ids = [sub.id for sub in area.subcategories.all()]
+ if map.subcategories.count():
+ sub_ids = [sub.id for sub in map.subcategories.all()]
subcategories = subcategories.filter(id__in=sub_ids)
selected_cats = [subcat.pk
- for subcat in area.default_subcategories.all()]
+ for subcat in map.default_subcategories.all()]
for sub_category in subcategories.order_by('order'):
if sub_category.category not in sub_categories:
sub_categories[sub_category.category] = []
@@ -273,10 +278,10 @@ class SubCategory(models.Model):
return subcategories
@classmethod
- def getAvailableTuples(cls, item_types=None, area_name=None):
+ def getAvailableTuples(cls, item_types=None, map_name=None):
cats = []
for cat, subcats in cls.getAvailable(item_types=item_types,
- area_name=area_name):
+ map_name=map_name):
cats.append((unicode(cat),
[(subcat.pk, subcat.name) for subcat in subcats]))
return cats
@@ -506,6 +511,8 @@ class Marker(GeographicItem):
null=True) # used by feeds
route = models.ForeignKey(u"Route", blank=True, null=True,
related_name='associated_marker')
+ weight = models.IntegerField(_(u"Quantity"), blank=True, null=True,
+ default=0)
description = models.TextField(_(u"Description"), blank=True, null=True)
is_front_page = models.NullBooleanField(_(u"Is front page"), blank=True,
null=True)
@@ -575,6 +582,10 @@ class Marker(GeographicItem):
def geom_attr(self):
return 'point'
+ @property
+ def has_weight(self):
+ return bool(self.categories.filter(weighted=True).count())
+
class Meta:
ordering = ('status', 'name')
verbose_name = _(u"Point of interest")
@@ -656,6 +667,32 @@ class Marker(GeographicItem):
val = values[unicode(propertymodel.id)]
self.setProperty(propertymodel, val)
+ PROPERTIES_KEYS = ['point', 'pk', 'name', 'weight']
+ @classmethod
+ def _getJson(cls, values, base_dct={"properties":{}}):
+ item = base_dct.copy()
+ item["geometry"] = {"type": "Point",
+ "coordinates": [values['point'].x,
+ values['point'].y]}
+ item["properties"]['pk'] = values['pk']
+ item["properties"]['name'] = values['name']
+ if values['weight']:
+ item["properties"]['weight'] = values['weight']
+ return item
+
+ def _getItems(self, base_dct={"properties":{}}):
+ '''Return a dict representation for json
+ '''
+ item = base_dct.copy()
+ item["geometry"] = {"type": "Point",
+ "coordinates": [ self.point.x, self.point.y ]
+ }
+ item["properties"]['pk'] = self.pk
+ item["properties"]['name'] = self.name
+ if self.weight:
+ item["properties"]['weight'] = self.weight
+ return item
+
def getGeoJSON(self, categories_id=[]):
'''Return a GeoJSON string
'''
@@ -673,11 +710,28 @@ class Marker(GeographicItem):
'icon_hover_path':cat.hover_icon.image \
if cat.hover_icon else '',
'category_name':cat.name})
+ items['weight'] = ''
+ if cat.weighted:
+ if not self.weight:
+ continue
+ items['weight'] = self.weight
+ if cat.color_theme and cat.color_theme.colors.count():
+ items['colors'] += ["#%s"] % '", "#'.join(
+ [color.code for color in cat.color_theme.colors.\
+ order_by('order').all()])
try:
items['properties'].update({'icon_width':cat.icon.image.width,
'icon_height':cat.icon.image.height,})
except IOError:
pass
+ if cat.weighted:
+ if not self.weight:
+ continue
+ items['weight'] = u', "weight":%d' % self.weight
+ if cat.color_theme and cat.color_theme.colors.count():
+ items['weight'] += u', "colors":["#%s"]' % '", "#'.join(
+ [color.code for color in cat.color_theme.colors.\
+ order_by('order').all()])
jsons.append(items)
@@ -691,20 +745,20 @@ class Marker(GeographicItem):
if cats.count():
return cats.all()[0]
- def get_absolute_url(self, area_name=''):
+ def get_absolute_url(self, map_name=''):
parameters = 'current_feature=%d' % self.id
if self.default_category:
parameters += '&checked_categories=%s' % self.default_category.pk
urn = TinyUrl.getUrnByParameters(parameters)
- area_name = area_name + '/' if area_name else ''
- url = reverse('chimere:tiny', args=[area_name, urn])
+ map_name = map_name + '/' if map_name else ''
+ url = reverse('chimere:tiny', args=[map_name, urn])
return url
PRE_ATTRS = {
'Marker':('name', 'geometry', 'import_version', 'modified_since_import'),
'Route':('name', 'geometry', 'import_version', 'modified_since_import'),
- 'Area':('urn', 'name'),
+ 'Map':('urn', 'name'),
}
def geometry_pre_save(cls, pre_save_geom_values):
def geom_pre_save(sender, **kwargs):
@@ -1142,6 +1196,13 @@ class Route(GeographicItem):
properties.append(property)
return properties
+ def _getItems(self, dct={'properties':{}}):
+ dct['geometry'] = { "type": "LineString",
+ "coordinates": [[point.x, point.y]
+ for point in self.route]}
+ dct['properties'].update({'id':self.id, 'name':self.name})
+ return dct
+
def getGeoJSON(self, color="#000"):
'''Return a GeoJSON string
'''
@@ -1282,14 +1343,14 @@ class SimpleArea:
return True
return False
- def getCategories(self, status='A', filter_available=True, area_name=None):
+ def getCategories(self, status='A', filter_available=True, map_name=None):
"""
- Get categories for this area
+ Get categories for this map
"""
wheres = []
- if area_name:
+ if map_name:
subcategory_pks = []
- for cat, subcats in SubCategory.getAvailable(area_name=area_name):
+ for cat, subcats in SubCategory.getAvailable(map_name=map_name):
for subcat in subcats:
subcategory_pks.append(unicode(subcat.pk))
if filter_available:
@@ -1353,37 +1414,43 @@ class Layer(models.Model):
class Meta:
verbose_name = _("Layer")
-class Area(models.Model, SimpleArea):
- """Rectangular area of the map
+class Map(models.Model, SimpleArea):
+ """A map
"""
name = models.CharField(_(u"Name"), max_length=150)
- urn = models.SlugField(_(u"Area urn"), max_length=50, blank=True,
+ available = models.BooleanField(_(u"Available"))
+ users = models.ManyToManyField(User, through='MapUsers')
+ urn = models.SlugField(_(u"Map urn"), max_length=50, blank=True,
unique=True)
welcome_message = models.TextField(_(u"Welcome message"), blank=True,
null=True)
order = models.IntegerField(_(u"Order"), unique=True)
- available = models.BooleanField(_(u"Available"))
upper_left_corner = models.PointField(_(u"Upper left corner"),
default='POINT(0 0)', srid=settings.CHIMERE_EPSG_DISPLAY_PROJECTION)
lower_right_corner = models.PointField(_(u"Lower right corner"),
default='POINT(0 0)', srid=settings.CHIMERE_EPSG_DISPLAY_PROJECTION)
- default = models.NullBooleanField(_(u"Default area"),
- help_text=_(u"Only one area is set by default"))
- layers = SelectMultipleField(Layer, related_name='areas',
- through='AreaLayers', blank=True)
+ default = models.BooleanField(_(u"Default map"), default=False,
+ help_text=_(u"Only one map is set by default"))
+ layers = SelectMultipleField(Layer, related_name='maps',
+ through='MapLayers', blank=True)
default_subcategories = SelectMultipleField(SubCategory, blank=True,
verbose_name=_(u"Sub-categories checked by default"))
dynamic_categories = models.NullBooleanField(
_(u"Sub-categories dynamicaly displayed"),
help_text=_(u"If checked, categories are only displayed in the menu if "
u"they are available on the current extent."))
- subcategories = SelectMultipleField(SubCategory, related_name='areas',
- blank=True, db_table='chimere_subcategory_areas',
+ subcategories = SelectMultipleField(SubCategory, related_name='maps',
+ blank=True, db_table='chimere_subcategory_maps',
verbose_name=_(u"Restricted to theses sub-categories"),
help_text=_(u"If no sub-category is set all sub-categories are "
u"available"))
external_css = models.URLField(_(u"Link to an external CSS"), blank=True,
null=True)
+ cluster = models.BooleanField(u"Clustering map (weight of items are added)",
+ default=False)
+ public_read = models.BooleanField(_(u"Public can read the map"))
+ public_propose = models.BooleanField(_(u"Public can propose item to the map"))
+ public_write = models.BooleanField(_(u"Public can write without moderation to the map"))
restrict_to_extent = models.BooleanField(_(u"Restrict to the area extent"),
default=False)
objects = models.GeoManager()
@@ -1393,13 +1460,72 @@ class Area(models.Model, SimpleArea):
class Meta:
ordering = ('order', 'name')
- verbose_name = _("Area")
+ verbose_name = _("Map")
+
+ def can_write(self, user=None):
+ return bool(self.getAvailable(user=user, urn=self.urn, single=True,
+ edit=True))
+
+ def can_propose(self, user=None):
+ return bool(self.getAvailable(user=user, urn=self.urn, single=True,
+ propose=True))
@classmethod
- def getAvailable(cls):
- '''Get available areas
+ def getAvailable(cls, user=None, urn=None, single=False, edit=False,
+ propose=False):
+ '''Get available maps
'''
- return cls.objects.filter(available=True)
+ map_filter = {'available':True}
+ if urn:
+ map_filter['urn'] = urn
+ elif single:
+ map_filter['default'] = True
+ filters = []
+ if not propose and not edit:
+ filters = [{'public_write':True},
+ {'public_propose':True},
+ {'public_read':True}]
+ elif propose:
+ filters = [{'public_write':True},
+ {'public_propose':True}]
+ elif edit:
+ filters = [{'public_write':True}]
+ if user and user.is_authenticated():
+ if not propose and not edit:
+ filters += [
+ {'mapusers__user':user, 'mapusers__read':True},
+ {'mapusers__user':user, 'mapusers__write':True},
+ {'mapusers__user':user, 'mapusers__propose':True},
+ {'mapgroups__group__user':user, 'mapgroups__read':True},
+ {'mapgroups__group__user':user, 'mapgroups__write':True},
+ {'mapgroups__group__user':user, 'mapgroups__propose':True}
+ ]
+ elif propose:
+ filters += [
+ {'mapusers__user':user, 'mapusers__write':True},
+ {'mapusers__user':user, 'mapusers__propose':True},
+ {'mapgroups__group__user':user, 'mapgroups__write':True},
+ {'mapgroups__group__user':user, 'mapgroups__propose':True}
+ ]
+ elif edit:
+ filters += [
+ {'mapusers__user':user, 'mapusers__write':True},
+ {'mapgroups__group__user':user, 'mapgroups__write':True},
+ ]
+ query = None
+ for fltr in filters:
+ fltr.update(map_filter)
+ if not query:
+ query = Q(**fltr)
+ else:
+ query = query | Q(**fltr)
+ maps = cls.objects.filter(query).distinct()
+ if single:
+ if not maps.count():
+ return
+ return maps.all()[0]
+ else:
+ return maps.all()
def getWkt(self):
return "SRID=%d;POLYGON((%f %f,%f %f,%f %f,%f %f, %f %f))" % (
@@ -1423,54 +1549,66 @@ class Area(models.Model, SimpleArea):
"""
return Q(route__contained=self.getWkt())
-pre_save_area_values = {}
-def area_pre_save(sender, **kwargs):
+pre_save_map_values = {}
+def map_pre_save(sender, **kwargs):
if not kwargs['instance']:
return
- geometry_pre_save(Area, pre_save_area_values)(sender, **kwargs)
-pre_save.connect(area_pre_save, sender=Area)
+ geometry_pre_save(Map, pre_save_map_values)(sender, **kwargs)
+pre_save.connect(map_pre_save, sender=Map)
-def area_post_save(sender, **kwargs):
+def map_post_save(sender, **kwargs):
if not kwargs['instance']:
return
- area = kwargs['instance']
- if area.default:
- defaults = Area.objects.filter(default=True).exclude(pk=area.pk)
+ map = kwargs['instance']
+ if map.default:
+ defaults = Map.objects.filter(default=True).exclude(pk=map.pk)
for default in defaults:
default.default = False
default.save()
# manage permissions
- old_urn, old_name = area.urn, area.name
- if area.pk in pre_save_area_values:
- old_urn, old_name = pre_save_area_values[area.pk]
+ old_urn, old_name = map.urn, map.name
+ if map.pk in pre_save_map_values:
+ old_urn, old_name = pre_save_map_values[map.pk]
perm, old_groups, old_users = None, [], []
- if area.urn != old_urn:
- oldmnemo = 'change_area_' + old_urn
+
+ if map.urn != old_urn:
+ oldmnemo = 'change_map_' + old_urn
old_perm = Permission.objects.filter(codename=oldmnemo)
if old_perm.count():
perm = old_perm.all()[0]
- perm.codename = 'change_area_' + area.urn
- perm.save()
- if not area.urn:
- area.urn = defaultfilters.slugify(area.name)
- area.save()
- mnemo = 'change_area_' + area.urn
+ codename = 'change_map_' + map.urn
+ if not Permission.objects.filter(codename=codename).count():
+ perm.codename = codename
+ perm.save()
+ if not map.urn:
+ map.urn = defaultfilters.slugify(map.name)
+ map.save()
+
+ # fix old mnemo
+ oldmnemo = 'change_area_' + old_urn
+ old_perm = Permission.objects.filter(codename=oldmnemo)
+ if old_perm.count():
+ perm = old_perm.all()[0]
+ perm.codename = 'change_map_' + map.urn
+ perm.save()
+
+ mnemo = 'change_map_' + map.urn
perm = Permission.objects.filter(codename=mnemo)
- lbl = "Can change " + area.name
+ lbl = "Can change " + map.name
if not perm.count():
content_type, created = ContentType.objects.get_or_create(
- app_label="chimere", model="area")
+ app_label="chimere", model="map")
perm = Permission(name=lbl, content_type_id=content_type.id,
codename=mnemo)
perm.save()
else:
perm = perm.all()[0]
- if old_name != area.name:
+ if old_name != map.name:
perm.name = lbl
perm.save()
# manage moderation group
- groupname = area.name + " moderation"
- if old_name != area.name:
+ groupname = map.name + " moderation"
+ if old_name != map.name:
old_groupname = old_name + " moderation"
old_gp = Group.objects.filter(name=old_groupname)
if old_gp.count():
@@ -1491,36 +1629,56 @@ def area_post_save(sender, **kwargs):
for p in Permission.objects.filter(content_type=ct).all():
group.permissions.add(p)
-post_save.connect(area_post_save, sender=Area)
+post_save.connect(map_post_save, sender=Map)
-def get_areas_for_user(user):
+def get_maps_for_user(user):
"""
- Getting subcats for a specific user
+ Getting maps for a specific user
"""
perms = user.get_all_permissions()
- areas = set()
- prefix = 'chimere.change_area_'
+ maps = set()
+ prefix = 'chimere.change_map_'
for perm in perms:
if perm.startswith(prefix):
try:
- area = Area.objects.get(urn=perm[len(prefix):])
- areas.add(area)
+ map = Map.objects.get(urn=perm[len(prefix):])
+ maps.add(map)
except ObjectDoesNotExist:
pass
- return areas
+ return maps
-def get_users_by_area(area):
- if not area:
+def get_users_by_map(map):
+ if not map:
return []
- perm = 'change_area_'+area.urn
+ perm = 'change_map_'+map.urn
return User.objects.filter(Q(groups__permissions__codename=perm)|
Q(user_permissions__codename=perm)).all()
-class AreaLayers(models.Model):
- area = models.ForeignKey(Area)
+class MapUsers(models.Model):
+ map = models.ForeignKey(Map, related_name='mapusers')
+ user = models.ForeignKey(User, related_name='mapusers')
+ read = models.BooleanField(_(u"Can read the map"))
+ propose = models.BooleanField(_(u"Can propose item to the map"))
+ write = models.BooleanField(_(u"Can write without moderation to the map"))
+ class Meta:
+ verbose_name = _("Map - user")
+ verbose_name_plural = _("Map - users")
+
+class MapGroups(models.Model):
+ map = models.ForeignKey(Map, related_name='mapgroups')
+ group = models.ForeignKey(Group, related_name='mapgroups')
+ read = models.BooleanField(_(u"Can read the map"))
+ propose = models.BooleanField(_(u"Can propose item to the map"))
+ write = models.BooleanField(_(u"Can write without moderation to the map"))
+ class Meta:
+ verbose_name = _("Map - group")
+ verbose_name_plural = _("Map - groups")
+
+class MapLayers(models.Model):
+ map = models.ForeignKey(Map)
layer = models.ForeignKey(Layer)
order = models.IntegerField(_(u"Order"))
- default = models.NullBooleanField(_(u"Default layer"))
+ default = models.BooleanField(_(u"Default layer"), default=False)
class Meta:
ordering = ('order',)
@@ -1534,6 +1692,7 @@ class PropertyModel(models.Model):
order = models.IntegerField(_(u"Order"))
available = models.BooleanField(_(u"Available"))
mandatory = models.BooleanField(_(u"Mandatory"))
+ slug = models.SlugField()
subcategories = SelectMultipleField(SubCategory, related_name='properties',
blank=True, verbose_name=_(u"Restricted to theses sub-categories"),
help_text=_(u"If no sub-category is set all the property applies to all "
@@ -1542,6 +1701,7 @@ class PropertyModel(models.Model):
('L', _('Long text')),
('P', _('Password')),
('D', _("Date")),
+ ('B', _("Boolean")),
('C', _("Choices")),
('B', _("Boolean")),
)
@@ -1560,8 +1720,7 @@ class PropertyModel(models.Model):
verbose_name = _("Property model")
def getAttrName(self):
- attr_name = defaultfilters.slugify(self.name)
- attr_name = re.sub(r'-','_', attr_name)
+ attr_name = self.slug.replace('-', '_')
return attr_name
def getNamedId(self):
@@ -1569,6 +1728,11 @@ class PropertyModel(models.Model):
'''
return 'property_%d_%d' % (self.order, self.id)
+ def save(self, *args, **kwargs):
+ if not self.slug:
+ self.slug = defaultfilters.slugify(self.name)
+ super(PropertyModel, self).save(*args, **kwargs)
+
class PropertyModelChoice(models.Model):
'''Choices for property model
'''
@@ -1589,15 +1753,19 @@ class Property(models.Model):
propertymodel = models.ForeignKey(PropertyModel,
verbose_name=_(u"Property model"))
value = models.TextField(_(u"Value"))
+
def __unicode__(self):
- if self.propertymodel.type == 'C':
+ if self.value and self.propertymodel.type == 'C':
try:
return unicode(PropertyModelChoice.objects.get(
pk=self.value).value)
- except self.DoesNotExist:
+ except (self.DoesNotExist, ValueError):
return ""
return unicode(self.value)
+ def label(self):
+ return unicode(self)
+
class Meta:
verbose_name = _(u"Property")
diff --git a/chimere/scripts/upgrade.py b/chimere/scripts/upgrade.py
deleted file mode 100755
index accb751..0000000
--- a/chimere/scripts/upgrade.py
+++ /dev/null
@@ -1,315 +0,0 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-
-import sys
-sys.path.append('.')
-
-from django.core.management import setup_environ
-import settings
-
-setup_environ(settings)
-
-from django.db import connection, transaction
-
-cursor = connection.cursor()
-
-from main.models import Area, Marker, Route, Icon, SubCategory
-from django.contrib.gis.geos import LineString
-
-# early versions before 0.1: urn field doesn't exist for area
-
-import htmlentitydefs, re
-
-def slugfy(text, separator):
- ret = u""
- text = text.strip()
- for c in text.lower():
- try:
- ret += htmlentitydefs.codepoint2name[ord(c)][0]
- except:
- ret += c
- ret = re.sub("\W", " ", ret)
- ret = re.sub(" +", separator, ret)
- return ret.strip()
-
-QUERY_CHECK_FIELD = """SELECT a.attname AS field FROM pg_class c, pg_attribute a
- WHERE c.relname = '%s' AND a.attnum > 0 AND a.attrelid = c.oid
- AND a.attname='%s';"""
-QUERY_CHECK_TABLE = """SELECT c.relname FROM pg_class c
-WHERE c.relname = '%s';"""
-
-
-query = QUERY_CHECK_FIELD % ('main_area', 'urn')
-cursor.execute(query)
-transaction.commit_unless_managed()
-
-row = cursor.fetchone()
-if not row:
- query_update = "ALTER TABLE main_area ADD COLUMN urn VARCHAR(50) \
-UNIQUE"
- cursor.execute(query_update)
- transaction.commit_unless_managed()
- areas = Area.objects.all()
- print " * urn field created in table main_area"
- for area in areas:
- urn = slugfy(area.name, "-")
- area.urn = urn
- area.save()
- print " * area %s urn is now: %s" % (area.name, area.urn)
-
- query = "ALTER TABLE main_area ALTER COLUMN urn SET not null;"
- cursor.execute(query)
- transaction.commit_unless_managed()
- print " * urn field has now the constraint NOT NULL"
-
-from django.contrib.auth.models import Permission, ContentType
-
-# check if permission have been correctly set for each areas
-areas = Area.objects.all()
-for area in areas:
- content_type = ContentType.objects.get(app_label="main",
- model="area")
- mnemo = 'change_area_' + area.urn
- perm = Permission.objects.filter(codename=mnemo)
- if not perm:
- lbl = "Can change " + area.name
- perm = Permission(name=lbl, content_type_id=content_type.id,
- codename=mnemo)
- perm.save()
- print ' * permission "' + lbl + '" has been created'
- print " WARNING: don't forget to update administrator's rights with \
-this new permission"
-
-# early versions before 0.1: subcategory_areas table doesn't exist
-# version 1.0 subcategory_areas renamed to main_subcategory_areas
-
-query = QUERY_CHECK_TABLE % 'main_subcategory_areas'
-cursor.execute(query)
-transaction.commit_unless_managed()
-
-row = cursor.fetchone()
-if not row:
- query = QUERY_CHECK_TABLE % 'subcategory_areas'
- cursor.execute(query)
- transaction.commit_unless_managed()
-
- row = cursor.fetchone()
- if row:
- query_rename = "ALTER TABLE subcategory_areas RENAME TO \
-main_subcategory_areas;"
- cursor.execute(query_rename)
- transaction.commit_unless_managed()
- print " * subcategory_areas renamed to main_subcategory_areas"
- else:
- query_create = """
-CREATE TABLE "main_subcategory_areas" (
- "id" serial NOT NULL PRIMARY KEY,
- "subcategory_id" integer NOT NULL REFERENCES "main_subcategory" ("id")
- DEFERRABLE INITIALLY DEFERRED,
- "area_id" integer NOT NULL REFERENCES "main_area" ("id")
- DEFERRABLE INITIALLY DEFERRED,
- UNIQUE ("subcategory_id", "area_id"));
-"""
- cursor.execute(query_create)
- transaction.commit_unless_managed()
- print " * main_subcategory_areas created"
-
-# early versions before 0.1: main_tinyurl table doesn't exist
-
-query = QUERY_CHECK_TABLE % 'main_tinyurl'
-cursor.execute(query)
-transaction.commit_unless_managed()
-
-row = cursor.fetchone()
-if not row:
- query_create = """
-CREATE TABLE "main_tinyurl" (
- "id" serial NOT NULL PRIMARY KEY,
- "parameters" varchar(500) NOT NULL);
-"""
- cursor.execute(query_create)
- transaction.commit_unless_managed()
- print " * main_tinyurl created"
-
-
-# early versions before 0.1: save area with wrong SRID
-# only errors with default SRID is managed adapt the script for your SRID
-
-from osgeo import osr
-
-srs = osr.SpatialReference()
-srs.ImportFromEPSG(4326) # WGS84
-ll = srs.CloneGeogCS()
-srs.ImportFromEPSG(settings.EPSG_PROJECTION)
-proj = osr.CoordinateTransformation(srs, ll)
-
-changed = False
-areas = Area.objects.all()
-for area in areas:
- # only one test: assume each point as been save with the same SRID...
- if area.upper_left_corner.srid == 4326 and area.upper_left_corner.x > 90 \
- or area.upper_left_corner.x < -90:
- changed = True
- pt = proj.TransformPoint(area.upper_left_corner.y,
- area.upper_left_corner.x)
- area.upper_left_corner.x = pt[0]
- area.upper_left_corner.y = pt[1]
- pt = proj.TransformPoint(area.lower_right_corner.y,
- area.lower_right_corner.x)
- area.lower_right_corner.x = pt[0]
- area.lower_right_corner.y = pt[1]
- area.save()
-if changed:
- print " * projections of areas corrected"
-
-# changement from version 1.0 to 1.1: add dated fields to markers and routes
-if settings.DAYS_BEFORE_EVENT:
- for cls in (Marker, Route):
- table = cls._meta.db_table
- query = QUERY_CHECK_FIELD % (table, 'start_date')
- cursor.execute(query)
- transaction.commit_unless_managed()
-
- row = cursor.fetchone()
- if not row:
- query_update = "ALTER TABLE "+table+" ADD COLUMN start_date date"
- cursor.execute(query_update)
- transaction.commit_unless_managed()
- query_update = "ALTER TABLE "+table+" ADD COLUMN end_date date"
- cursor.execute(query_update)
- transaction.commit_unless_managed()
- print " * start_date and end_date added to table " + table + "."
-
-# changement from version 1.0 to 1.1: add available_date field to marker
-if 'chimere.rss' in settings.INSTALLED_APPS:
- for cls in (Marker,):
- table = cls._meta.db_table
- query = QUERY_CHECK_FIELD % (table, 'available_date')
- cursor.execute(query)
- transaction.commit_unless_managed()
-
- row = cursor.fetchone()
- if not row:
- query_update = "ALTER TABLE " + table + " ADD COLUMN \
-available_date timestamp with time zone"
- cursor.execute(query_update)
- transaction.commit_unless_managed()
- print " * available_date added to table " + table + "."
-
-# changement from version 1.0 to 1.1: version of django 1.2
-# create specific height and width for image fields
-
-for cls, attr in ((Icon, "image"), (Marker, "picture"),
- (Route, "picture")):
- table = cls._meta.db_table
- query = QUERY_CHECK_FIELD % (table, 'width')
- cursor.execute(query)
- transaction.commit_unless_managed()
-
- row = cursor.fetchone()
- if not row:
- query_update = "ALTER TABLE "+table+" ADD COLUMN width integer"
- cursor.execute(query_update)
- transaction.commit_unless_managed()
- query_update = "ALTER TABLE "+table+" ADD COLUMN height integer"
- cursor.execute(query_update)
- transaction.commit_unless_managed()
- for obj in cls.objects.all():
- image = getattr(obj, attr)
- if not image:
- continue
- obj.width = image.width
- obj.height = image.height
- obj.save()
- print " * height and width of " + table + " corrected"
-
-# changement from version 1.0 to 1.1: multiple selection of categories
-
-for cls in (Marker, Route):
- table = cls._meta.db_table[len("main_"):]
- query = QUERY_CHECK_TABLE % ('main_' + table + '_categories')
- cursor.execute(query)
- transaction.commit_unless_managed()
-
- row = cursor.fetchone()
- if row:
- continue
- query_create = """
-CREATE TABLE "main_%s_categories" (
- "id" serial NOT NULL PRIMARY KEY,
- "%s_id" integer NOT NULL REFERENCES "main_%s" ("id") DEFERRABLE INITIALLY DEFERRED,
- "subcategory_id" integer NOT NULL REFERENCES "main_subcategory" ("id") DEFERRABLE INITIALLY DEFERRED,
- UNIQUE ("%s_id", "subcategory_id"));
-""" % (table, table, table, table)
- cursor.execute(query_create)
- transaction.commit_unless_managed()
- for obj in cls.objects.all():
- query = "select subcategory_id from main_%s where id=%d" % (table,
- obj.id)
- cursor.execute(query)
- transaction.commit_unless_managed()
-
- row = cursor.fetchone()
- if row:
- obj.categories.add(SubCategory.objects.get(id=row[0]))
- obj.save()
- query = "ALTER TABLE main_%s DROP COLUMN subcategory_id;" % table
- cursor.execute(query)
- transaction.commit_unless_managed()
- print " * main_%s_categories created" % table
-
-# -> version 1.2: associate point to route (for the future)
-query = QUERY_CHECK_FIELD % ('main_marker', 'route_id')
-cursor.execute(query)
-transaction.commit_unless_managed()
-
-row = cursor.fetchone()
-if not row:
- query_update = 'ALTER TABLE "main_marker" ADD COLUMN \
-"route_id" integer REFERENCES "main_route" ("id") DEFERRABLE INITIALLY DEFERRED'
- cursor.execute(query_update)
- transaction.commit_unless_managed()
- print " * route_id added to table main_marker."
-
-# -> version 1.3: file associated to routes
-query = QUERY_CHECK_TABLE % 'main_routefile'
-cursor.execute(query)
-transaction.commit_unless_managed()
-
-row = cursor.fetchone()
-if not row:
- query_create = """
- CREATE TABLE "main_routefile" (
- "id" serial NOT NULL PRIMARY KEY,
- "name" varchar(150) NOT NULL,
- "raw_file" varchar(100) NOT NULL,
- "simplified_file" varchar(100),
- "file_type" varchar(1) NOT NULL
- )
- ;
- ALTER TABLE "main_route" ADD COLUMN
- "associated_file_id" integer REFERENCES "main_routefile" ("id")
- DEFERRABLE INITIALLY DEFERRED;
- """
- cursor.execute(query_create)
- transaction.commit_unless_managed()
- print " * main_routefile created"
-
-# early versions before 0.1: save route with wrong SRID
-# only errors with default SRID is managed adapt the script for your SRID
-
-changed = False
-routes = Route.objects.all()
-for route in routes:
- # only one test: assume each point as been save with the same SRID...
- if route.route and route.route.srid == 4326 and \
- route.route[0][0] > 90 or route.route[0][0] < -90:
- changed = True
- new_route = []
- for pt in route.route:
- pt = proj.TransformPoint(pt[0], pt[1])
- new_route.append((pt[0], pt[1]))
- route.route = LineString(new_route)
- route.save()
-if changed:
- print " * projections of routes corrected"
diff --git a/chimere/settings.sample.py b/chimere/settings.sample.py
index 1ddef5f..1ddeca6 100644
--- a/chimere/settings.sample.py
+++ b/chimere/settings.sample.py
@@ -1,7 +1,8 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
-# This file is an example of settings.py for a Chimère project
+# Don't edit this file:
+# overload all theses settings in your local_settings.py file
import os, sys
_ = lambda s: s
@@ -27,8 +28,6 @@ JQUERY_JS_URLS = ('/javascript/jquery/jquery.js',
JQUERY_CSS_URLS = ('/javascript/jquery-ui/css/smoothness/jquery-ui.css',
'/javascript/jquery-ui-themes/base/jquery.ui.all.css')
-OSM_CSS_URLS = ["http://www.openlayers.org/api/theme/default/style.css"]
-
GPSBABEL = '/usr/bin/gpsbabel'
GPSBABEL_OPTIONS = 'simplify,crosstrack,error=0.005k' # simplify with an
# error of 5 meters
@@ -44,8 +43,8 @@ CHIMERE_EPSG_PROJECTION = 900913
# projection displayed to the end user by openlayers
# chimere use the same projection to save its data in the database
CHIMERE_EPSG_DISPLAY_PROJECTION = 4326
-# display of shortcuts for areas
-CHIMERE_DISPLAY_AREAS = True
+# display of shortcuts for maps
+CHIMERE_DISPLAY_MAPS = True
# number of day before an event to display
# if equal to 0: disable event management
# if you change this value from 0 to a value in a production environnement
@@ -114,6 +113,13 @@ est trop dangereux.</p>"""
NOMINATIM_URL = 'http://nominatim.openstreetmap.org/search'
+# Fields used to geolocate with nominatim (http://wiki.openstreetmap.org/wiki/Nominatim#Search)
+# from external sources. Key must be a valid parameters for Nominatim. If given
+# value are property slugs use the corresponding value otherwise use it as a
+# fixed value
+# e.g : {'street':'address', 'city':'city', 'country':u'France'}
+CHIMERE_NOMINATIM_FIELDS = {}
+
# thumbnail
CHIMERE_THUMBS_SCALE_HEIGHT=250
CHIMERE_THUMBS_SCALE_WIDTH=None
@@ -129,6 +135,8 @@ HAYSTACK_CONNECTIONS = {
CHIMERE_CSV_ENCODING = 'ISO-8859-1'
+CHIMERE_DEFAULT_ACTIONS = ['view', 'contribute', 'rss', 'contact']
+
# generic contact email
CONTACT_EMAIL = ''
@@ -283,6 +291,9 @@ LOGGING = {'version': 1,
},
}
+CHIMERE_VIEW_RENDERER = 'openlayers' # 'openlayers' or 'leaflet'
+CHIMERE_EDIT_RENDERER = 'openlayers' # 'openlayers'
+
try:
from local_settings import *
except ImportError, e:
@@ -306,13 +317,44 @@ if 'CHIMERE_SHARE_NETWORKS' not in globals():
STATIC_URL + 'chimere/img/identica.png'),
)
-if 'OSM_JS_URLS' not in globals():
- global OSM_JS_URLS
- OSM_JS_URLS = [STATIC_URL + "openlayers/OpenLayers.js",
- STATIC_URL + "openlayers/SimplePanZoom.js",
- "http://www.openstreetmap.org/openlayers/OpenStreetMap.js"]
-if 'OSM_MOBILE_JS_URLS' not in globals():
- global OSM_MOBILE_JS_URLS
- OSM_MOBILE_JS_URLS = [STATIC_URL + "openlayers/OpenLayers.mobile.js",
- STATIC_URL + "openlayers/SimplePanZoom.js",
- "http://www.openstreetmap.org/openlayers/OpenStreetMap.js"]
+if 'MAP_JS_URLS' not in globals():
+ global MAP_JS_URLS
+ MAP_JS_URLS = {
+ 'openlayers':[
+ STATIC_URL + "openlayers/OpenLayers.js",
+ STATIC_URL + "openlayers/SimplePanZoom.js",
+ "http://www.openstreetmap.org/openlayers/OpenStreetMap.js",
+ STATIC_URL + "chimere/js/jquery.chimere-ol.js"
+ ],
+ 'leaflet':[
+ STATIC_URL + "leaflet/leaflet.js",
+ STATIC_URL + "chimere/js/jquery.chimere-leaflet.js",
+ "http://maps.stamen.com/js/tile.stamen.js?v1.2.1"
+ ]
+ }
+ if CHIMERE_ENABLE_CLUSTERING:
+ MAP_JS_URLS['leaflet'] += [
+ STATIC_URL + "leaflet-markercluster/leaflet.markercluster.js"
+ ]
+if 'MAP_CSS_URLS' not in globals():
+ global MAP_CSS_URLS
+ MAP_CSS_URLS = {
+ 'openlayers':["http://www.openlayers.org/api/theme/default/style.css"],
+ 'leaflet':[STATIC_URL + "leaflet/leaflet.css",
+ STATIC_URL + "leaflet-markercluster/MarkerCluster.css",
+ STATIC_URL + "leaflet-markercluster/MarkerCluster.Default.css",
+ ]
+ }
+ # key: [(url, condition)]
+ MAP_CONDITIONNAL_CSS_URLS = {
+ 'openlayers':[("http://www.openlayers.org/api/theme/default/style.css",
+ None)],
+ 'leaflet':[
+ (STATIC_URL + "leaflet/leaflet.css", None),
+ (STATIC_URL + "leaflet/leaflet.ie.css", 'lte IE 8'),
+ (STATIC_URL + "leaflet-markercluster/MarkerCluster.css", None),
+ (STATIC_URL + "leaflet-markercluster/MarkerCluster.Default.css", None),
+ (STATIC_URL + "leaflet-markercluster/MarkerCluster.Default.ie.css",
+ 'lte IE 8'),
+ ]
+ }
diff --git a/chimere/src/default-bg.xcf b/chimere/src/default-bg.xcf
new file mode 100644
index 0000000..71ad247
--- /dev/null
+++ b/chimere/src/default-bg.xcf
Binary files differ
diff --git a/chimere/src/default_bg.xcf b/chimere/src/default_bg.xcf
new file mode 100644
index 0000000..453bf15
--- /dev/null
+++ b/chimere/src/default_bg.xcf
Binary files differ
diff --git a/chimere/src/marker-cluster-large.xcf b/chimere/src/marker-cluster-large.xcf
new file mode 100755
index 0000000..ac0e14d
--- /dev/null
+++ b/chimere/src/marker-cluster-large.xcf
Binary files differ
diff --git a/chimere/src/marker-cluster-medium.xcf b/chimere/src/marker-cluster-medium.xcf
new file mode 100755
index 0000000..85de110
--- /dev/null
+++ b/chimere/src/marker-cluster-medium.xcf
Binary files differ
diff --git a/chimere/src/marker-cluster-small.xcf b/chimere/src/marker-cluster-small.xcf
new file mode 100755
index 0000000..3e38c15
--- /dev/null
+++ b/chimere/src/marker-cluster-small.xcf
Binary files differ
diff --git a/chimere/src/marker-cluster-xlarge.xcf b/chimere/src/marker-cluster-xlarge.xcf
new file mode 100755
index 0000000..6c9c890
--- /dev/null
+++ b/chimere/src/marker-cluster-xlarge.xcf
Binary files differ
diff --git a/chimere/src/marker-cluster-xsmall.xcf b/chimere/src/marker-cluster-xsmall.xcf
new file mode 100755
index 0000000..f0787cc
--- /dev/null
+++ b/chimere/src/marker-cluster-xsmall.xcf
Binary files differ
diff --git a/chimere/src/marker-cluster-xxlarge.xcf b/chimere/src/marker-cluster-xxlarge.xcf
new file mode 100755
index 0000000..4c9be7c
--- /dev/null
+++ b/chimere/src/marker-cluster-xxlarge.xcf
Binary files differ
diff --git a/chimere/static/chimere/img/share-icon.xcf b/chimere/src/share-icon.xcf
index d149768..d149768 100644..100755
--- a/chimere/static/chimere/img/share-icon.xcf
+++ b/chimere/src/share-icon.xcf
Binary files differ
diff --git a/chimere/static/chimere/css/styles.css b/chimere/static/chimere/css/styles.css
index fd39b79..dd27d95 100644
--- a/chimere/static/chimere/css/styles.css
+++ b/chimere/static/chimere/css/styles.css
@@ -32,6 +32,11 @@ h2, h3, th, .action li, .action li a,
color:#aaa;
}
+.ui-autocomplete-loading {
+ background:url('../img/ajax-loader-small.gif') no-repeat right center;
+}
+
+.nominatim-search,
#chimere_total_label td.l{
color:#000;
}
@@ -52,9 +57,10 @@ body, h2, h3, th,
background-image:None;
}
+#no-content .alert,
fieldset, .action li, #content,
#layer_selection #layer_list,
-#map-footer, #panel, #chimere_itinerary_panel, #areas,
+#map-footer, #panel, #chimere_itinerary_panel, #maps,
#welcome, #detail, .detail_footer a,
#content .olControlLayerSwitcher .layersDiv,
#content .olControlLayerSwitcher span,
@@ -79,7 +85,7 @@ div.warning, .errorlist, .errorlist legend{
/* entête */
/*
-#areas h2, #panel h2{
+#maps h2, #panel h2{
-moz-border-radius: 4px 4px 0 0;
-webkit-border-radius: 4px 4px 0 0;
border-radius: 4px 4px 0 0;
@@ -93,7 +99,7 @@ h2, #action li, .detail_footer{
}
fieldset, #content, #panel,
-#areas, #welcome, #detail,
+#maps, #welcome, #detail,
#category_description, div.warning{
-moz-border-radius: 10px;
-webkit-border-radius: 10px;
@@ -225,6 +231,28 @@ ul#action-2 {
padding:20px;
}
+#no-content{
+ position:absolute;
+ bottom:0;
+ top:0;
+ right:0;
+ left:0;
+ background-image:url(../img/default-bg.jpg)
+}
+
+#no-content .alert{
+ width:400px;
+ padding:0.5em;
+ margin-left:auto;
+ margin-right:auto;
+ margin-top:25%;
+ padding:0.5em 1em;
+}
+
+#auth p{
+ text-align:center;
+}
+
#footer{
margin:5px;
text-align:center;
@@ -260,23 +288,20 @@ ul#action-2 {
width:100%;
}
-#areas-div,
-#areas-div label,
+#maps-div,
+#maps-div label,
#utils-div a{
padding:0.3em;
}
-#areas-div label{
+#maps-div label{
color: #777;
font-variant:small-caps;
}
-#areas{
+#maps{
position:absolute;
z-index:5;
-}
-
-#areas{
bottom:105px;
left:18px;
padding:0;
@@ -285,12 +310,12 @@ ul#action-2 {
overflow:auto;
}
-#areas ul{
+#maps ul{
margin:0;
padding:0 10px;
}
-#areas li{
+#maps li{
list-style:none;
}
@@ -591,6 +616,10 @@ ul.share li{
font-style:italic;
}
+.nominatim-search{
+ font-style:normal;
+}
+
.simple #panel{
top:5px;
bottom:auto;
@@ -715,6 +744,11 @@ p.warning{
padding:0.5em;
}
+p.submit{
+ border-top-width:1px;
+ text-align:center;
+}
+
#no-js-message{
z-index:5000;
text-align:center;
@@ -1184,3 +1218,154 @@ div.pp_default .pp_expand{
left:6px;
bottom:5px;
}
+
+.marker-cluster.marker-cluster-xxlarge {
+ border-radius: 27px;
+}
+
+.marker-cluster.marker-cluster-xlarge {
+ border-radius: 25px;
+}
+
+.marker-cluster.marker-cluster-large {
+ border-radius: 22px;
+}
+
+.marker-cluster.marker-cluster-medium {
+ border-radius: 20px;
+}
+
+.marker-cluster.marker-cluster-small {
+ border-radius: 17px;
+}
+
+.marker-cluster.marker-cluster-xsmall {
+ border-radius: 15px;
+}
+
+.marker-cluster.marker-cluster-xxlarge div {
+ width: 44px;
+ height: 44px;
+ border-radius: 22px;
+}
+
+.marker-cluster.marker-cluster-xlarge div {
+ width: 40px;
+ height: 40px;
+ border-radius: 20px;
+}
+
+.marker-cluster.marker-cluster-large div {
+ width: 34px;
+ height: 34px;
+ border-radius: 17px;
+}
+
+.marker-cluster.marker-cluster-medium div {
+ width: 30px;
+ height: 30px;
+ border-radius: 15px;
+}
+
+.marker-cluster.marker-cluster-small div {
+ width: 24px;
+ height: 24px;
+ border-radius: 12px;
+}
+
+.marker-cluster.marker-cluster-xsmall div {
+ width: 20px;
+ height: 20px;
+ border-radius: 10px;
+}
+
+.marker-cluster.marker-cluster-xxlarge span {
+ line-height: 44px;
+}
+
+.marker-cluster.marker-cluster-xlarge span {
+ line-height: 40px;
+}
+
+.marker-cluster.marker-cluster-large span {
+ line-height: 34px;
+}
+
+.marker-cluster.marker-cluster-small span {
+ line-height: 24px;
+}
+
+.marker-cluster.marker-cluster-xsmall span {
+ line-height: 20px;
+}
+
+.marker-cluster-xsmall-number,
+.marker-cluster-small-number,
+.marker-cluster-medium-number,
+.marker-cluster-large-number
+.marker-cluster-xlarge-number
+.marker-cluster-xxlarge-number{
+ position: relative;
+ font: 12px "Helvetica Neue", Arial, Helvetica, sans-serif;
+ text-align: center;
+}
+
+.marker-cluster-xxlarge-number{
+ width: 55px;
+ top: -35px;
+}
+
+.marker-cluster-xlarge-number{
+ width: 50px;
+ top: -32px;
+}
+
+.marker-cluster-large-number{
+ width: 45px;
+ top: -30px;
+}
+
+.marker-cluster-medium-number{
+ width: 40px;
+ top: -27px;
+}
+
+.marker-cluster-small-number{
+ width: 35px;
+ top: -25px;
+}
+
+.marker-cluster-xsmall-number{
+ width: 30px;
+ top: -22px;
+}
+
+.marker-cluster-xsmall {
+ background-color: rgba(221, 255, 180, 0.6);
+}
+
+.marker-cluster-xsmall div {
+ background-color: rgba(150, 244, 97, 0.6);
+}
+
+.marker-cluster-large {
+ background-color: rgba(255, 214, 140, 0.6);
+}
+.marker-cluster-large div {
+ background-color: rgba(255, 183, 54, 0.6);
+}
+
+.marker-cluster-xlarge {
+ background-color: rgba(253, 156, 115, 0.6);
+}
+.marker-cluster-xlarge div {
+ background-color: rgba(241, 128, 23, 0.6);
+}
+
+.marker-cluster-xxlarge {
+ background-color: rgba(255, 174, 176, 0.6);
+}
+.marker-cluster-xxlarge div {
+ background-color: rgba(255, 48, 54, 0.6);
+}
+
diff --git a/chimere/static/chimere/img/ajax-loader-small.gif b/chimere/static/chimere/img/ajax-loader-small.gif
new file mode 100644
index 0000000..085ccae
--- /dev/null
+++ b/chimere/static/chimere/img/ajax-loader-small.gif
Binary files differ
diff --git a/chimere/static/chimere/img/default-bg.jpg b/chimere/static/chimere/img/default-bg.jpg
new file mode 100644
index 0000000..a3e96f0
--- /dev/null
+++ b/chimere/static/chimere/img/default-bg.jpg
Binary files differ
diff --git a/chimere/static/chimere/img/marker-cluster-large.png b/chimere/static/chimere/img/marker-cluster-large.png
new file mode 100644
index 0000000..eef6a73
--- /dev/null
+++ b/chimere/static/chimere/img/marker-cluster-large.png
Binary files differ
diff --git a/chimere/static/chimere/img/marker-cluster-medium.png b/chimere/static/chimere/img/marker-cluster-medium.png
new file mode 100644
index 0000000..1d9099b
--- /dev/null
+++ b/chimere/static/chimere/img/marker-cluster-medium.png
Binary files differ
diff --git a/chimere/static/chimere/img/marker-cluster-small.png b/chimere/static/chimere/img/marker-cluster-small.png
new file mode 100644
index 0000000..0620694
--- /dev/null
+++ b/chimere/static/chimere/img/marker-cluster-small.png
Binary files differ
diff --git a/chimere/static/chimere/img/marker-cluster-xlarge.png b/chimere/static/chimere/img/marker-cluster-xlarge.png
new file mode 100644
index 0000000..477e2b8
--- /dev/null
+++ b/chimere/static/chimere/img/marker-cluster-xlarge.png
Binary files differ
diff --git a/chimere/static/chimere/img/marker-cluster-xsmall.png b/chimere/static/chimere/img/marker-cluster-xsmall.png
new file mode 100644
index 0000000..748b3ed
--- /dev/null
+++ b/chimere/static/chimere/img/marker-cluster-xsmall.png
Binary files differ
diff --git a/chimere/static/chimere/img/marker-cluster-xxlarge.png b/chimere/static/chimere/img/marker-cluster-xxlarge.png
new file mode 100644
index 0000000..d7b435f
--- /dev/null
+++ b/chimere/static/chimere/img/marker-cluster-xxlarge.png
Binary files differ
diff --git a/chimere/static/chimere/js/base.js b/chimere/static/chimere/js/base.js
index d7a9695..49751b2 100644
--- a/chimere/static/chimere/js/base.js
+++ b/chimere/static/chimere/js/base.js
@@ -17,8 +17,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
See the file COPYING for details.
*/
-var default_area;
-var area_name;
+var default_map;
+var map_name;
/* indexOf definition for old IE versions */
if(!Array.indexOf){
@@ -64,7 +64,7 @@ function saveExtent() {
/* save the current extent in a cookie */
if(!map) return;
var extent_key = 'MAP_EXTENT';
- if (area_name){ extent_key = extent_key + '_' + area_name; }
+ if (map_name){ extent_key = extent_key + '_' + map_name; }
var extent = map.getExtent().transform(map.getProjectionObject(),
epsg_display_projection);
document.cookie = extent_key + "=" + extent.toArray().join('_')
@@ -76,7 +76,7 @@ function getExtent() {
var cookies = document.cookie.split(';');
var map_extent;
var extent_key = 'MAP_EXTENT';
- if (area_name){ extent_key = extent_key + '_' + area_name; }
+ if (map_name){ extent_key = extent_key + '_' + map_name; }
for (var i=0; i < cookies.length; i++){
var items = cookies[i].split('=');
key = items[0].split(' ').join('');
@@ -95,9 +95,9 @@ function zoomToCurrentExtent(map){
extent = new OpenLayers.Bounds(current_extent[0], current_extent[1],
current_extent[2], current_extent[3]);
}
- else if (OpenLayers && default_area && default_area.length == 4){
- extent = new OpenLayers.Bounds(default_area[0], default_area[1],
- default_area[2], default_area[3]);
+ else if (OpenLayers && default_map && default_map.length == 4){
+ extent = new OpenLayers.Bounds(default_map[0], default_map[1],
+ default_map[2], default_map[3]);
}
else{
return;
diff --git a/chimere/static/chimere/js/jquery.chimere-leaflet.js b/chimere/static/chimere/js/jquery.chimere-leaflet.js
new file mode 100644
index 0000000..14c0688
--- /dev/null
+++ b/chimere/static/chimere/js/jquery.chimere-leaflet.js
@@ -0,0 +1,707 @@
+/* Copyright (C) 2013 Étienne Loks <etienne.loks_AT_peacefrogsDOTnet>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+See the file COPYING for details.
+*/
+
+/*
+* Little hasattr helper
+*/
+(function ($) {
+ $.hasattr = function (key, arr) {
+ var v = arr[key];
+ if (typeof v === "undefined")
+ return false;
+ else
+ return v; };
+})( jQuery );
+
+(function ($) {
+ /*
+ * Chimere leaflet jQuery plugin
+ */
+ /*
+ * Default settings
+ */
+ var defaults = {
+ restricted_extent: false,
+ permalink_label: null,
+ permalink_div: null,
+ permalink: null, // OL Control, could be overrided
+ map_layers: null,
+ map_layer_names: null,
+ selected_map_layer: null,
+ dynamic_categories: false,
+ display_submited: false,
+ display_feature: null,
+ display_route: null,
+ map_id: null,
+ checked_categories: [],
+ zoom: null,
+ lat: null,
+ lon: null,
+ simple: false,
+ routing_start_lat: null,
+ routing_start_lon: null,
+ routing_end_lat: null,
+ routing_end_lon: null,
+ routing_steps_lonlat: null,
+ // Provide this function to make a custom click event on the marker
+ on_marker_click: null,
+ // Provide this function to override the feature detail display
+ display_feature_detail_fx: null,
+ // Provide this function for overriding the getSubcategories default
+ get_subcategories_fx: null,
+ hide_popup_fx: null,
+ // if leave to false every click on the map hide the pop-up
+ explicit_popup_hide: false,
+ popupClass: null,
+ popupContentFull: false, // if true the detail is inside the popup
+ category_accordion: true, // category opening behave like an accordion
+ maxResolution: 156543.0399,
+ units: 'm',
+ projection: 4326,
+ theme: null,
+ enable_clustering: false,
+ clustering_map: false, // weigth of items are added
+ routing: false, // enable routing management
+ routing_panel_open: function(){
+ $('#chimere_itinerary_panel').dialog('open');
+ },
+ current_feature: null, // To store the active POI
+ current_control: null, // To store the current control
+ current_popup: null, // To store the current POI popup displayed
+ current_category: null, // To store the current category clicked in list
+ current_route_feature: null, // To store the current route find by routing
+ itinerary_step_number:0, // current step number
+ icon_offset: (0, 0),
+ edition: false, // edition mode
+ edition_type_is_route: false, // route or POI edition
+ default_icon: ('http://www.openlayers.org/dev/img/marker-green.png',
+ (21, 25), (-(21/2), -25)),
+ cluster_icon: null,
+ marker_hover_id:'marker_hover',
+ marker_hover_content_id:'marker_hover_content',
+ marker_hover_offset: null,
+ icon_start: null,
+ icon_step: null,
+ icon_end: null,
+ weight_steps: new Array(10, 50, 100, 500, 1000),
+ weight_icon_sizes: new Array(30, 34, 40, 44, 50, 54),
+ weight_icon_classes: new Array('marker-cluster-xsmall',
+ 'marker-cluster-small',
+ 'marker-cluster-medium',
+ 'marker-cluster-large',
+ 'marker-cluster-xlarge',
+ 'marker-cluster-xxlarge'
+ ),
+ extra_json_data: null
+ };
+ var settings = {};
+ /*
+ * Publics methods
+ */
+ var methods = {
+ /*
+ * Plugin init function
+ */
+ init: function ( options ) {
+ settings = $.extend({}, defaults);
+ if ( options ) $.extend(settings, options);
+ var map_element = $(this).attr('id');
+
+ settings.map = map = L.map(map_element);
+ map.addLayer(settings.map_layers[0]);
+ var map_layers = {};
+ for (idx=0 ; idx < settings.map_layers.length ; idx++){
+ map_layers[settings.map_layer_names[idx]] = settings.map_layers[idx];
+ }
+ var layer_control_options = {};
+ if (settings.simple){
+ layer_control_options['position'] = 'topleft';
+ }
+ settings.layer_control = L.control.layers(map_layers, null, layer_control_options).addTo(map);
+ if(settings.zoom && settings.lat && settings.lon){
+ map.setView([settings.lat, settings.lon], settings.zoom);
+ } else {
+ map.fitWorld();
+ }
+
+ settings.icons = new Object();
+ if (settings.clustering_map){
+ settings.NumberedDivIcon = L.Icon.extend({
+ options: {
+ iconUrl: STATIC_URL + 'chimere/img/empty.png',
+ number: '',
+ shadowUrl: null,
+ iconSize: new L.Point(40, 40),
+ iconAnchor: new L.Point(20, 20),
+ popupAnchor: new L.Point(0, -20),
+ className: '',
+ numberClassName: settings.weight_icon_classes[0]+'-number',
+ },
+
+ createIcon: function () {
+ var div = document.createElement('div');
+ var img = this._createImg(this.options['iconUrl']);
+ var numdiv = document.createElement('div');
+ numdiv.setAttribute( "class", this.options['numberClassName']);
+ numdiv.innerHTML = this.options['number'] || '';
+ div.appendChild ( img );
+ div.appendChild ( numdiv );
+ this._setIconStyles(div, 'icon');
+ return div;
+ },
+ createShadow: function () {
+ return null;
+ }
+ });
+
+ settings.layerMarkers = new L.MarkerClusterGroup({
+ spiderfyOnMaxZoom: false, showCoverageOnHover: false,
+ maxClusterRadius:50,
+ iconCreateFunction : function (cluster) {
+ var markers = cluster.getAllChildMarkers();
+ var weight = 0;
+ for (idx=0;idx<markers.length;idx++){
+ weight += markers[idx].options.weight;
+ }
+ var idx = settings.weight_steps.length;
+ for (i=0; i < settings.weight_steps.length; i++){
+ if (weight < settings.weight_steps[i]) {
+ idx = i;
+ break;
+ }
+ }
+ return new L.DivIcon({
+ html: '<div><span>' + weight + '</span></div>',
+ className: 'marker-cluster ' + settings.weight_icon_classes[idx],
+ iconSize: new L.Point(settings.weight_icon_sizes[idx],
+ settings.weight_icon_sizes[idx])
+ });
+ }
+ });
+ } else {
+ if (settings.enable_clustering){
+ settings.layerClusters = new L.MarkerClusterGroup({
+ showCoverageOnHover: false});
+ }
+ settings.layerMarkers = L.geoJson(null, {
+ onEachFeature: function (feature, layer) {
+ if (feature.properties && feature.properties.name) {
+ layer.bindPopup(feature.properties.name);
+ }
+ },
+ pointToLayer: function (feature, latlng) {
+ var marker = null;
+
+ if (feature.properties.weight){
+ var fill_color = "#ff7800";
+ if (feature.properties.colors){
+ var idx = feature.properties.weight/5;
+ if (idx < feature.properties.colors.length){
+ fill_color = feature.properties.colors[idx];
+ } else {
+ fill_color = feature.properties.colors[feature.properties.colors.length-1];
+ }
+ }
+ var geojsonMarkerOptions = {
+ radius: feature.properties.weight*2,
+ fillColor: fill_color,
+ color: "#000",
+ weight: feature.properties.weight/2,
+ opacity: 1,
+ fillOpacity: 0.8
+ };
+ marker = L.circleMarker(latlng, geojsonMarkerOptions);
+ } else {
+ var icon_path = MEDIA_URL + feature.properties.icon_path;
+ if (!settings.icons.hasOwnProperty(icon_path)){
+ var icon_offset = null;
+ if (feature.properties.icon_offset){
+ icon_offset = feature.properties.icon_offset;
+ } else {
+ icon_offset = [feature.properties.icon_width/2,
+ feature.properties.icon_height];
+ }
+ var popup_anchor = null;
+ if (feature.properties.popup_anchor){
+ popup_anchor = feature.properties.popup_anchor;
+ } else {
+ popup_anchor = [0,
+ -feature.properties.icon_height];
+ }
+ settings.icons[icon_path] = L.icon({
+ iconUrl: icon_path,
+ iconSize: [feature.properties.icon_width,
+ feature.properties.icon_height],
+ iconAnchor: icon_offset,
+ popupAnchor: popup_anchor
+ });
+ }
+ marker = L.marker(latlng,
+ {icon: settings.icons[icon_path]});
+ }/*
+ if (settings.enable_clustering){
+ settings.layerClusters.addLayer(marker);
+ return settings.layerClusters;
+ } else {*/
+ marker.properties = feature.properties;
+ return marker;
+ //}
+ }
+ }).addTo(map);
+ map.on('popupopen', function(e) {
+ var marker = e.popup._source;
+ methods.display_feature_detail(marker.properties.pk);
+ });
+ }
+ settings.layerMarkers.addTo(map);
+ settings.layerVectors = L.geoJson().addTo(map);
+
+ methods.loadCategories();
+ methods.loadGeoObjects();
+ if (settings.extra_json_data){
+ for (idx=0;idx<settings.extra_json_data.length;idx++){
+ $.ajax({
+ dataType: "json",
+ url: settings.extra_json_data[idx]['url'],
+ context: settings.extra_json_data[idx],
+ success: function(data) {
+ var geojsonLayer = new L.GeoJSON(data,{
+ style:this['style']
+ });
+ settings.layer_control.addOverlay(geojsonLayer,
+ this['name']);
+ if (this['selected']){
+ geojsonLayer.addTo(settings.map);
+ }
+ }
+ });
+ }
+ }
+ },
+ hidePopup: function (evt) {
+ settings.map.closePopup();
+ return true;
+ },
+ /*
+ * Update the categories div in ajax
+ */
+ loadCategories: function () {
+ var current_extent = settings.map.getBounds();
+ current_extent = current_extent.toBBoxString();
+ current_extent = current_extent.split(',').join('_');
+ current_extent = current_extent.replace(/\./g, 'D');
+ current_extent = current_extent.replace(/-/g, 'M');
+ var uri = extra_url
+ if (settings.map_id) uri += settings.map_id + "/";
+ uri += "getAvailableCategories/";
+ var params = {"current_extent": current_extent}
+ if (settings.display_submited) params["status"] = "A_S";
+ $.ajax({url: uri,
+ data: params,
+ cache: false,
+ success: function (data) {
+ $('#categories').empty();
+ $('#categories').html(data);
+ _init_categories();
+ _reCheckCategories();
+ if (settings.current_category) {
+ // TODO : add a force mode
+ // (in case the category is yet visible in HTML...)
+ methods.toggle_category();
+ }
+ }
+ });
+ var _toggle_subcategories = function (category_element) {
+ // Check subcategories only if unchecked
+ var val = category_element.is(":checked") ? true : false;
+ category_element.parent().find("li input").attr("checked", val);
+ }
+ var _toggle_categories = function (subcategory_element) {
+ var parent = subcategory_element.closest('ul');
+ var parent_label = parent.parent().find("> span");
+ if (parent.find('input[type=checkbox]:checked').length){
+ parent_label.addClass('category-selected');
+ } else {
+ parent_label.removeClass('category-selected');
+ }
+ var master_check = parent.find("> input");
+ if (parent.find('.subcategories input[type=checkbox]').length ==
+ parent.find('.subcategories input[type=checkbox]:checked').length){
+ master_check.attr('checked', 'checked');
+ } else {
+ master_check.removeAttr('checked');
+ }
+
+ if($('#action-categories').length){
+ if ($('#categories input[type=checkbox]:checked').length){
+ $('#action-categories').addClass('category-selected');
+ } else {
+ $('#action-categories').removeClass('category-selected');
+ }
+ }
+ return master_check;
+ };
+ var _init_categories = function () {
+ /*
+ * Add event listener in categories DOM elements
+ */
+ $('#categories #ul_categories > li > input').bind("click",
+ function (e) {
+ methods.hidePopup(e);
+ _toggle_subcategories($(this));
+ methods.loadGeoObjects();
+ //settings.permalink.updateLink();
+ });
+ $('.subcategories li input').bind("click", function (e) {
+ var c_name = $(this).attr('name');
+ c_name = c_name.substr(c_name.lastIndexOf("_")+1);
+ if($(this).is(':checked')){
+ methods.subcategory_detail(c_name);
+ }
+ var par = $(this).parent();
+ if ($(this).attr('checked')){
+ par.addClass('selected');
+ } else {
+ par.removeClass('selected');
+ }
+ methods.hidePopup(e);
+ methods.loadGeoObjects();
+ _toggle_categories($(this));
+ //settings.permalink.updateLink();
+ if ($('#layer_cat_'+c_name).length){
+ $('#layer_cat_'+c_name).prop("checked",
+ this.checked);
+ }
+ });
+ $('#display_submited_check').bind("click", function () {
+ methods.loadGeoObjects();
+ //settings.permalink.updateLink();
+ });
+ // Zoom to category
+ $(".zoom_to_category").bind("click", function (e) {
+ var id = this.id.substr(this.id.lastIndexOf("_")+1);
+ helpers.zoom_to_category(id);
+ });
+ $(".zoom_to_subcategory").bind("click", function (e) {
+ var id = this.id.substr(this.id.lastIndexOf("_")+1);
+ helpers.zoom_to_subcategories([id]);
+ });
+ $(".toggle_category").parent().bind("click", function (e) {
+ var item = $(this).children('.toggle_category');
+ var id = item.attr('id').substr(item.attr('id').lastIndexOf("_")+1);
+ methods.toggle_category(id);
+ });
+ }
+ var _reCheckCategories = function (){
+ /* recheck categories on init or when a redraw occurs */
+ if (!settings.checked_categories){
+ return;
+ }
+ $('#frm_categories .subcategories input:checkbox').each(function(index){
+ cat_id = $(this).attr('id').split('_').pop();
+ if (settings.checked_categories.indexOf(parseInt(cat_id)) != -1) {
+ $(this).attr("checked", "checked");
+ _toggle_categories($(this));
+ methods.toggle_category();
+ } else {
+ $(this).attr("checked", false);
+ }
+ });
+ if (settings.display_submited == true){
+ $('#display_submited_check').attr("checked", "checked");
+ }
+ }
+ },
+ /*
+ * Load markers and route from DB
+ */
+ loadGeoObjects: function () {
+ if ($('#waiting').length){
+ $('#waiting').show();
+ }
+ helpers.retrieve_checked_categories();
+ var ids = settings.checked_categories.join('_');
+ if (!ids) ids = '0';
+ var uri = extra_url + "getGeoObjects/" + ids;
+ if (settings.display_submited) uri += "/A_S";
+ $.ajax({url: uri,
+ dataType: "json",
+ success: function (data) {
+ settings.map.removeLayer(settings.layerMarkers);
+ settings.map.removeLayer(settings.layerVectors);
+ settings.layerMarkers.clearLayers();
+ settings.layerVectors.clearLayers();
+ if (settings.enable_clustering && !settings.clustering_map){
+ settings.map.removeLayer(settings.layerClusters);
+ settings.layerClusters.clearLayers();
+ }
+ if (settings.clustering_map){
+ for (var i = 0; i < data.features.length; i++) {
+ var feature = data.features[i];
+ if (feature.geometry.type == 'Point'){
+ var weight = 1;
+ if (feature.properties.weight){
+ weight = feature.properties.weight;
+ }
+ var idx = 2;
+ if (weight < settings.weight_steps[0]) {
+ idx = 0;
+ } else if (weight < settings.weight_steps[1]) {
+ idx = 1;
+ }
+ var size = settings.weight_icon_sizes[idx];
+ var icon = new settings.NumberedDivIcon({
+ number: weight,
+ iconSize: new L.Point(size, size),
+ iconAnchor: new L.Point(size/2, size/2),
+ popupAnchor: new L.Point(0, -size/2),
+ iconUrl:STATIC_URL + "chimere/img/" + settings.weight_icon_classes[idx] + ".png",
+ numberClassName:settings.weight_icon_classes[idx] + "-number"
+ });
+ var marker = new L.Marker(
+ new L.LatLng(
+ feature.geometry.coordinates[1],
+ feature.geometry.coordinates[0]),
+ { title: feature.properties.name,
+ weight: weight,
+ icon:icon});
+ marker.bindPopup(feature.properties.name);
+ settings.layerMarkers.addLayer(marker);
+ }
+ /*else if (feature.geometry.type == 'LineString') {
+ methods.addRoute(feature);
+ } else if (feature.geometry.type == 'MultiLineString') {
+ methods.addMultiLine(feature);
+ }*/
+ }
+ } else {
+ settings.layerMarkers.addData(data);
+ }
+ settings.map.addLayer(settings.layerMarkers);
+ settings.map.addLayer(settings.layerVectors);
+ if (settings.enable_clustering && !settings.clustering_map){
+ settings.map.addLayer(settings.layerClusters);
+ }
+ },
+ error: function (data, textStatus, errorThrown) {
+ settings.layerMarkers.clearLayers();
+ settings.layerVectors.clearLayers();
+ },
+ complete: function () {
+ if($('#waiting').length){$('#waiting').hide();}
+ }
+ });
+ },
+ subcategory_detail: function(category_id){
+ var uri = extra_url + "getCategory/" + category_id;
+
+ $.ajax({url: uri,
+ dataType: "json",
+ success: function (data) {
+ if (!data.description){return}
+ $('#category_description').html(data.description);
+ $("#category_description").dialog("option", "title",
+ data.name);
+ $('#category_description').dialog('open');
+ },
+ error: function (data) {
+ // fail silently
+ }
+ });
+ },
+ toggle_category: function (id) {
+ // TODO make this id DOM element customisable
+ // Check if element is currently visible or not
+ var was_visible = $("#maincategory_" + id).is(":visible");
+ // Close all categories
+ var category_plus = STATIC_URL + "chimere/img/plus.png";
+ var category_minus = STATIC_URL + "chimere/img/minus.png";
+ if (settings.category_accordion){
+ $("#categories ul.subcategories").hide();
+ $("#categories img.toggle_category").attr("src", category_plus);
+ $("#categories .main_category").addClass("toggle_plus");
+ $("#categories .main_category").removeClass("toggle_minus");
+ }
+ // Put a minus image
+ if (!was_visible)
+ {
+ // Show the subcategories
+ $("#maincategory_" + id).toggle();
+ $("#maincategory_" + id).parent().addClass("toggle_minus");
+ $("#maincategory_" + id).parent().removeClass("toggle_plus");
+ // Put a plus image
+ $("#maincategory_img_" + id).attr("src", category_minus);
+ settings.current_category = id;
+ }
+ if (!settings.category_accordion && was_visible)
+ {
+ $("#maincategory_" + id).toggle();
+ $("#maincategory_" + id).parent().addClass("toggle_plus");
+ $("#maincategory_" + id).parent().removeClass("toggle_minus");
+ // Put a minus image
+ $("#maincategory_img_" + id).attr("src", category_plus);
+ }
+ },
+ zoom: function (options) {
+ if ($.hasattr("category", options)) {
+ helpers.zoom_to_category(options["category"]);
+ } else if ($.hasattr("subcategories", options)) {
+ helpers.zoom_to_subcategories(options["subcategories"]);
+ } else if ($.hasattr("area", options)) {
+ helpers.zoom_to_area(options["area"]);
+ }
+ },
+ display_feature_detail: function (pk) {
+ /*
+ * update current detail panel with an AJAX request
+ */
+ var uri = extra_url
+ if (settings.map_id) uri += settings.map_id + "/"
+ uri += "getDetail/" + pk;
+ var params = {}
+ if (settings.simple) { params["simple"] = 1; }
+ $.ajax({url: uri,
+ data: params,
+ dataType: "html",
+ success: function (data) {
+ if ( settings.display_feature_detail_fx ) {
+ // Custom function ?
+ settings.display_feature_detail_fx(data, settings);
+ }
+ else {
+ if (settings.popupContentFull || settings.simple) {
+ settings.map._popup.options.maxWidth = 550;
+ settings.map._popup.setContent("<div class='cloud'>" + data + "</div>");
+ settings.map._popup.update();
+ }
+ else {
+ $('#detail').html(data).show();
+ }
+ }
+ }
+ });
+ },
+ }; // End of public methods
+ var helpers = {
+ getSubcategories: function (category_id) {
+ if(settings.get_subcategories_fx) {
+ return settings.get_subcategories_fx(category_id, settings);
+ }
+ else {
+ var ul = document.getElementById('maincategory_'+category_id);
+ var subcats = new Array();
+ for (i in ul.children){
+ var li = ul.children[i];
+ if (li.id){
+ subcats.push(li.id.split('_').pop());
+ }
+ }
+ return subcats;
+ }
+ },
+ retrieve_checked_categories: function () {
+ /*
+ * Retrieve checked_categories, and store it in settings
+ */
+ var initialized = false;
+ $('#frm_categories .subcategories input:checkbox').each(
+ function(index){
+ if (!initialized){
+ initialized = true;
+ settings.checked_categories = [];
+ settings.display_submited = false;
+ }
+ if ($(this).attr('checked') == 'checked' || $(this).attr('checked') == true){
+ cat_id = $(this).attr('id').split('_').pop();
+ settings.checked_categories.push(parseInt(cat_id));
+ }
+ });
+ if(initialized && ($('#display_submited_check').attr("checked") == "checked" || $('#display_submited_check').attr("checked") == true)){
+ settings.display_submited = true;
+ }
+ },
+ zoom_to: function (bounds) {
+ settings.map.fitBounds(bounds);
+ },
+ zoom_to_subcategories: function (ids) {
+ // TODO add markers and check the subcategory, if not yet checked/displayed
+ var ids = ids.join('_');
+ if (!ids) ids = '0';
+ var uri = extra_url + "getGeoObjects/" + ids;
+ if (settings.display_submited) uri += "/A_S";
+ $.ajax({url: uri,
+ dataType: "json",
+ success: function (data) {
+ // Create a generic bounds
+ var lon, lat, feature;
+ var points = new Array();
+ for (var i = 0; i < data.features.length; i++) {
+ feature = data.features[i];
+ if (feature.geometry.type == 'Point') {
+ lat = feature.geometry.coordinates[1];
+ lon = feature.geometry.coordinates[0];
+ points.push(settings.map.latLngToLayerPoint([lat, lon]));
+ } else if (feature.geometry.type == 'LineString') {
+ // TODO
+ }
+ }
+ var bound = L.Bounds(points);
+ helpers.zoom_to(bounds);
+ }
+ });
+ },
+ zoom_to_category: function (id) {
+ helpers.zoom_to_subcategories(helpers.getSubcategories(id));
+ },
+ zoom_to_area: function (coords) {
+ /* zoom to an area */
+ helpers.zoom_to([[coords[1], coords[0]],
+ [coords[3], coords[2]]]);
+ if (settings.dynamic_categories) {
+ methods.loadCategories();
+ }
+ }
+ }; // End of helpers
+
+ $.fn.chimere = function (thing) {
+ // Method calling logic
+ if ( methods[thing] ) {
+ return methods[ thing ].apply( this, Array.prototype.slice.call( arguments, 1 ));
+ }
+ else if ( typeof thing === 'object' || ! thing ) {
+ return methods.init.apply( this, arguments );
+ }
+ else if ( thing === 'settings' ) {
+ // Use $("#mydiv").chimere("settings", "key", "value") to change settings
+ // from outside the plugin
+ if (arguments.length == 3) {
+ settings[arguments[1]] = arguments[2];
+ }
+ else if (arguments.length == 2) {
+ return settings[arguments[1]];
+ }
+ else { // Use $("#mydiv").chimere("settings") to know the current settings
+ return settings;
+ }
+ }
+ else {
+ $.error( 'Method ' + thing + ' does not exist on jQuery.chimere' );
+ }
+ return this;
+ };
+})( jQuery );
diff --git a/chimere/static/chimere/js/jquery.chimere.js b/chimere/static/chimere/js/jquery.chimere-ol.js
index d85490f..9346964 100644
--- a/chimere/static/chimere/js/jquery.chimere.js
+++ b/chimere/static/chimere/js/jquery.chimere-ol.js
@@ -17,20 +17,22 @@ See the file COPYING for details.
*/
/* Add OpenLayers MapQuest layer management */
-OpenLayers.Layer.MapQuestOSM = OpenLayers.Class(OpenLayers.Layer.XYZ, {
- name: "MapQuestOSM",
- sphericalMercator: true,
- url: ' http://otile1.mqcdn.com/tiles/1.0.0/osm/${z}/${x}/${y}.png',
- clone: function(obj) {
- if (obj == null) {
- obj = new OpenLayers.Layer.OSM(
- this.name, this.url, this.getOptions());
- }
- obj = OpenLayers.Layer.XYZ.prototype.clone.apply(this, [obj]);
- return obj;
- },
- CLASS_NAME: "OpenLayers.Layer.MapQuestOSM"
-});
+if (typeof(OpenLayers) != 'undefined'){
+ OpenLayers.Layer.MapQuestOSM = OpenLayers.Class(OpenLayers.Layer.XYZ, {
+ name: "MapQuestOSM",
+ sphericalMercator: true,
+ url: ' http://otile1.mqcdn.com/tiles/1.0.0/osm/${z}/${x}/${y}.png',
+ clone: function(obj) {
+ if (obj == null) {
+ obj = new OpenLayers.Layer.OSM(
+ this.name, this.url, this.getOptions());
+ }
+ obj = OpenLayers.Layer.XYZ.prototype.clone.apply(this, [obj]);
+ return obj;
+ },
+ CLASS_NAME: "OpenLayers.Layer.MapQuestOSM"
+ });
+};
/*
* Little hasattr helper
@@ -62,7 +64,7 @@ OpenLayers.Layer.MapQuestOSM = OpenLayers.Class(OpenLayers.Layer.XYZ, {
display_submited: false,
display_feature: null,
display_route: null,
- area_id: null,
+ map_id: null,
checked_categories: [],
zoom: null,
lat: null,
@@ -90,7 +92,7 @@ OpenLayers.Layer.MapQuestOSM = OpenLayers.Class(OpenLayers.Layer.XYZ, {
category_accordion: true, // category opening behave like an accordion
maxResolution: 156543.0399,
units: 'm',
- projection: new OpenLayers.Projection('EPSG:4326'),
+ projection: null,
theme: null,
enable_clustering: false,
routing: false, // enable routing management
@@ -103,13 +105,10 @@ OpenLayers.Layer.MapQuestOSM = OpenLayers.Class(OpenLayers.Layer.XYZ, {
current_category: null, // To store the current category clicked in list
current_routes_features: [], // To store the current routes find by routing
itinerary_step_number:0, // current step number
- icon_offset: new OpenLayers.Pixel(0, 0),
+ icon_offset: null,
edition: false, // edition mode
edition_type_is_route: false, // route or POI edition
- default_icon: new OpenLayers.Icon(
- 'http://www.openlayers.org/dev/img/marker-green.png',
- new OpenLayers.Size(21, 25),
- new OpenLayers.Pixel(-(21/2), -25)),
+ default_icon: null,
cluster_icon: null,
marker_hover_id:'marker_hover',
marker_hover_content_id:'marker_hover_content',
@@ -130,6 +129,27 @@ OpenLayers.Layer.MapQuestOSM = OpenLayers.Class(OpenLayers.Layer.XYZ, {
*/
init: function ( options ) {
/* Manage parameters */
+ if (defaults.controls == null){
+ defaults.controls = [new OpenLayers.Control.Navigation(),
+ new OpenLayers.Control.PanPanel(),
+ new OpenLayers.Control.ZoomPanel(),
+ new OpenLayers.Control.ScaleLine()];
+ }
+ if (defaults.popupClass == null){
+ defaults.popupClass = OpenLayers.Popup.FramedCloud;
+ }
+ if (defaults.projection == null){
+ defaults.projection = new OpenLayers.Projection('EPSG:4326');
+ }
+ if (defaults.icon_offset == null){
+ defaults.icon_offset = new OpenLayers.Pixel(0, 0);
+ }
+ if (defaults.default_icon == null){
+ defaults.default_icon = new OpenLayers.Icon(
+ 'http://www.openlayers.org/dev/img/marker-green.png',
+ new OpenLayers.Size(21, 25),
+ new OpenLayers.Pixel(-(21/2), -25));
+ }
// not staticaly in default because of STATIC_URL init
if (defaults.cluster_icon == null && typeof STATIC_URL != 'undefined'){
defaults.cluster_icon = new OpenLayers.Icon(
@@ -681,7 +701,7 @@ OpenLayers.Layer.MapQuestOSM = OpenLayers.Class(OpenLayers.Layer.XYZ, {
current_extent = current_extent.replace(/\./g, 'D');
current_extent = current_extent.replace(/-/g, 'M');
var uri = extra_url
- if (settings.area_id) uri += settings.area_id + "/";
+ if (settings.map_id) uri += settings.map_id + "/";
uri += "getAvailableCategories/";
var params = {"current_extent": current_extent}
if (settings.display_submited) params["status"] = "A_S";
@@ -1068,6 +1088,18 @@ OpenLayers.Layer.MapQuestOSM = OpenLayers.Class(OpenLayers.Layer.XYZ, {
settings.map.getProjectionObject());
settings.layerVectors.addFeatures(feats);
},
+ pointInputChange: function(nominatim_id){
+ methods.putEditMarker(new OpenLayers.LonLat(
+ $('#'+nominatim_id+'_lon').val(),
+ $('#'+nominatim_id+'_lat').val()
+ ).transform(EPSG_DISPLAY_PROJECTION,
+ settings.map.getProjectionObject()), false);
+ var bounds = settings.layerMarkers.getDataExtent();
+ if (bounds) {
+ settings.map.zoomToExtent(bounds);
+ settings.map.zoomTo(13);
+ }
+ },
routingInputChange: function(nominatim_id){
$('#map_menu_clear').show();
switch(nominatim_id){
@@ -1361,7 +1393,7 @@ OpenLayers.Layer.MapQuestOSM = OpenLayers.Class(OpenLayers.Layer.XYZ, {
* update current detail panel with an AJAX request
*/
var uri = extra_url
- if (settings.area_id) uri += settings.area_id + "/"
+ if (settings.map_id) uri += settings.map_id + "/"
uri += "getDetail/" + pk;
var params = {}
if (settings.simple) { params["simple"] = 1; }
diff --git a/chimere/static/chimere/js/nominatim-widget.js b/chimere/static/chimere/js/nominatim-widget.js
index 99f7034..d61443f 100644
--- a/chimere/static/chimere/js/nominatim-widget.js
+++ b/chimere/static/chimere/js/nominatim-widget.js
@@ -1,4 +1,5 @@
var default_nominatim_lbl = '';
+var routing = false;
var nominatim_widget_options = {
source: function (request, response) {
$.ajax({
@@ -30,7 +31,11 @@ var nominatim_widget_options = {
$('#chimere_'+$(this).attr('id').substring(10)+'_label').html(
ui.item.label);
$('#'+$(this).attr('id')).val(default_nominatim_lbl);
- jQuery("#map").chimere("routingInputChange", $(this).attr('id'));
+ if (routing){
+ jQuery("#"+chimere_init_options['map_id']).chimere("routingInputChange", $(this).attr('id'));
+ } else {
+ jQuery("#"+chimere_init_options['map_id']).chimere("pointInputChange", $(this).attr('id'));
+ }
return false;
},
open: function() {
diff --git a/chimere/static/leaflet-markercluster/CHANGELOG.md b/chimere/static/leaflet-markercluster/CHANGELOG.md
new file mode 100644
index 0000000..8113107
--- /dev/null
+++ b/chimere/static/leaflet-markercluster/CHANGELOG.md
@@ -0,0 +1,49 @@
+Leaflet.markercluster
+=====================
+
+(all changes without author notice are by [@danzel](https://github.com/danzel))
+
+## Master
+
+### Improvements
+
+ * Work better with custom projections (by [@andersarstrand](https://github.com/andersarstrand)) [#74](https://github.com/danzel/Leaflet.markercluster/issues/74)
+ * Add custom getBounds that works (Reported by [@2803media](https://github.com/2803media))
+ * Allow spacing spiderfied icons further apart (Reported by [@stevevance](https://github.com/stevevance)) [#100](https://github.com/danzel/Leaflet.markercluster/issues/100)
+ * Add custom eachLayer that works (Reported by [@cilogi](https://github.com/cilogi)) [#102](https://github.com/danzel/Leaflet.markercluster/issues/102)
+
+### Bugfixes
+
+ * Fix singleMarkerMode when you aren't on the map (by [@duncanparkes](https://github.com/duncanparkes)) [#77](https://github.com/danzel/Leaflet.markercluster/issues/77)
+ * Fix clearLayers when you aren't on the map (by [@duncanparkes](https://github.com/duncanparkes)) [#79](https://github.com/danzel/Leaflet.markercluster/issues/79)
+ * IE10 Bug fix (Reported by [@theLundquist](https://github.com/theLundquist)) [#86](https://github.com/danzel/Leaflet.markercluster/issues/86)
+ * Fixes for hasLayer after removing a layer (Reported by [@cvisto](https://github.com/cvisto)) [#44](https://github.com/danzel/Leaflet.markercluster/issues/44)
+ * Fix clearLayers not unsetting __parent of the markers, preventing them from being re-added. (Reported by [@apuntovanini](https://github.com/apuntovanini)) [#99](https://github.com/danzel/Leaflet.markercluster/issues/99)
+ * Fix map.removeLayer(markerClusterGroup) not working (Reported by [@Driklyn](https://github.com/Driklyn)) [#108](https://github.com/danzel/Leaflet.markercluster/issues/108)
+ * Fix map.addLayers not updating cluster icons (Reported by [@Driklyn](https://github.com/Driklyn)) [#114](https://github.com/danzel/Leaflet.markercluster/issues/114)
+ * Fix spiderfied clusters breaking if a marker is added to them (Reported by [@Driklyn](https://github.com/Driklyn)) [#114](https://github.com/danzel/Leaflet.markercluster/issues/114)
+
+## 0.2 (2012-10-11)
+
+### Improvements
+
+ * Add addLayers/removeLayers bulk add and remove functions that perform better than the individual methods
+ * Allow customising the polygon generated for showing the area a cluster covers (by [@yohanboniface](https://github.com/yohanboniface)) [#68](https://github.com/danzel/Leaflet.markercluster/issues/68)
+ * Add zoomToShowLayer method to zoom down to a marker then call a callback once it is visible
+ * Add animateAddingMarkers to allow disabling animations caused when adding/removing markers
+ * Add hasLayer
+ * Pass the L.MarkerCluster to iconCreateFunction to give more flexibility deciding the icon
+ * Make addLayers support geojson layers
+ * Allow disabling clustering at a given zoom level
+ * Allow styling markers that are added like they were clusters of size 1
+
+
+### Bugfixes
+
+ * Support when leaflet is configured to use canvas rather than SVG
+ * Fix some potential crashes in zoom handlers
+ * Tidy up when we are removed from the map
+
+## 0.1 (2012-08-16)
+
+Initial Release! \ No newline at end of file
diff --git a/chimere/static/leaflet-markercluster/MIT-LICENCE.txt b/chimere/static/leaflet-markercluster/MIT-LICENCE.txt
new file mode 100644
index 0000000..19af068
--- /dev/null
+++ b/chimere/static/leaflet-markercluster/MIT-LICENCE.txt
@@ -0,0 +1,20 @@
+Copyright 2012 David Leaver
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/chimere/static/leaflet-markercluster/MarkerCluster.Default.css b/chimere/static/leaflet-markercluster/MarkerCluster.Default.css
new file mode 100644
index 0000000..90558dd
--- /dev/null
+++ b/chimere/static/leaflet-markercluster/MarkerCluster.Default.css
@@ -0,0 +1,38 @@
+.marker-cluster-small {
+ background-color: rgba(181, 226, 140, 0.6);
+ }
+.marker-cluster-small div {
+ background-color: rgba(110, 204, 57, 0.6);
+ }
+
+.marker-cluster-medium {
+ background-color: rgba(241, 211, 87, 0.6);
+ }
+.marker-cluster-medium div {
+ background-color: rgba(240, 194, 12, 0.6);
+ }
+
+.marker-cluster-large {
+ background-color: rgba(253, 156, 115, 0.6);
+ }
+.marker-cluster-large div {
+ background-color: rgba(241, 128, 23, 0.6);
+ }
+
+.marker-cluster {
+ background-clip: padding-box;
+ border-radius: 20px;
+ }
+.marker-cluster div {
+ width: 30px;
+ height: 30px;
+ margin-left: 5px;
+ margin-top: 5px;
+
+ text-align: center;
+ border-radius: 15px;
+ font: 12px "Helvetica Neue", Arial, Helvetica, sans-serif;
+ }
+.marker-cluster span {
+ line-height: 30px;
+ } \ No newline at end of file
diff --git a/chimere/static/leaflet-markercluster/MarkerCluster.Default.ie.css b/chimere/static/leaflet-markercluster/MarkerCluster.Default.ie.css
new file mode 100644
index 0000000..1d0de51
--- /dev/null
+++ b/chimere/static/leaflet-markercluster/MarkerCluster.Default.ie.css
@@ -0,0 +1,22 @@
+ /* IE 6-8 fallback colors */
+.marker-cluster-small {
+ background-color: rgb(181, 226, 140);
+ }
+.marker-cluster-small div {
+ background-color: rgb(110, 204, 57);
+ }
+
+.marker-cluster-medium {
+ background-color: rgb(241, 211, 87);
+ }
+.marker-cluster-medium div {
+ background-color: rgb(240, 194, 12);
+ }
+
+.marker-cluster-large {
+ background-color: rgb(253, 156, 115);
+ }
+.marker-cluster-large div {
+ background-color: rgb(241, 128, 23);
+}
+
diff --git a/chimere/static/leaflet-markercluster/MarkerCluster.css b/chimere/static/leaflet-markercluster/MarkerCluster.css
new file mode 100644
index 0000000..dbfab7d
--- /dev/null
+++ b/chimere/static/leaflet-markercluster/MarkerCluster.css
@@ -0,0 +1,6 @@
+.leaflet-cluster-anim .leaflet-marker-icon, .leaflet-cluster-anim .leaflet-marker-shadow {
+ -webkit-transition: -webkit-transform 0.2s ease-out, opacity 0.2s ease-in;
+ -moz-transition: -moz-transform 0.2s ease-out, opacity 0.2s ease-in;
+ -o-transition: -o-transform 0.2s ease-out, opacity 0.2s ease-in;
+ transition: transform 0.2s ease-out, opacity 0.2s ease-in;
+ }
diff --git a/chimere/static/leaflet-markercluster/README.md b/chimere/static/leaflet-markercluster/README.md
new file mode 100644
index 0000000..b2f44b6
--- /dev/null
+++ b/chimere/static/leaflet-markercluster/README.md
@@ -0,0 +1,125 @@
+Leaflet.markercluster
+=====================
+
+Provides Beautiful Animated Marker Clustering functionality for [Leaflet](http://leafletjs.com), a JS library for interactive maps.
+
+*Requires Leaflet 0.4.2 or newer*
+
+## Using the plugin
+See the included examples for usage.
+
+The [realworld example](http://leaflet.github.com/Leaflet.markercluster/example/marker-clustering-realworld.388.html) is a good place to start, it uses all of the defaults of the clusterer.
+Or check out the [custom example](http://leaflet.github.com/Leaflet.markercluster/example/marker-clustering-custom.html) for how to customise the behaviour and appearance of the clusterer
+
+### Usage
+Create a new MarkerClusterGroup, add your markers to it, then add it to the map
+
+```javascript
+var markers = new L.MarkerClusterGroup();
+markers.addLayer(new L.Marker(getRandomLatLng(map)));
+... Add more layers ...
+map.addLayer(markers);
+```
+
+### Defaults
+By default the Clusterer enables some nice defaults for you:
+showCoverageOnHover: When you mouse over a cluster it shows the bounds of its markers.
+zoomToBoundsOnClick: When you click a cluster we zoom to its bounds.
+spiderfyOnMaxZoom: When you click a cluster at the bottom zoom level we spiderfy it so you can see all of its markers.
+removeOutsideVisibleBounds: Clusters and markers too far from the viewport are removed from the map for performance.
+
+You can disable any of these as you want in the options when you create the MarkerClusterGroup:
+```javascript
+var markers = new L.MarkerClusterGroup({ spiderfyOnMaxZoom: false, showCoverageOnHover: false, zoomToBoundsOnClick: false });
+```
+
+### Customising the Clustered Markers
+As an option to MarkerClusterGroup you can provide your own function for creating the Icon for the clustered markers.
+The default implementation changes color at bounds of 10 and 100, but more advanced uses may require customising this.
+You do not need to include the .Default css if you go this way.
+You are passed a MarkerCluster object, you'll probably want to use getChildCount() or getAllChildMarkers() to work out the icon to show
+
+```javascript
+var markers = new L.MarkerClusterGroup({
+ iconCreateFunction: function(cluster) {
+ return new L.DivIcon({ html: '<b>' + cluster.getChildCount() + '</b>' });
+ }
+});
+```
+Check out the [custom example](http://leaflet.github.com/Leaflet.markercluster/example/marker-clustering-custom.html) for an example of this.
+
+### All Options
+Enabled by default (boolean options):
+* **zoomToBoundsOnClick**: When you click a cluster we zoom to its bounds.
+* **showCoverageOnHover**: When you mouse over a cluster it shows the bounds of its markers.
+* **spiderfyOnMaxZoom**: When you click a cluster at the bottom zoom level we spiderfy it so you can see all of its markers.
+
+Other options
+* **animateAddingMarkers**: If set to true then adding individual markers to the MarkerClusterGroup after it has been added to the map will add the marker and animate it in to the cluster. Defaults to false as this gives better performance when bulk adding markers.
+* **disableClusteringAtZoom**: If set, at this zoom level and below markers will not be clustered. This defaults to disabled. [See Example](http://leaflet.github.com/Leaflet.markercluster/example/marker-clustering-realworld-maxzoom.388.html)
+* **maxClusterRadius**: The maximum radius that a cluster will cover from the central marker (in pixels). Default 80. Decreasing will make more smaller clusters.
+* **polygonOptions**: Options to pass when creating the L.Polygon to show the bounds of a cluster
+* **singleMarkerMode**: If set to true, overrides the icon for all added markers to make them appear as a 1 size cluster
+* **spiderfyDistanceMultiplier**: Increase from 1 to increase the distance away from the center that spiderfied markers are placed. Use if you are using big marker icons.
+
+## Events
+If you register for click, mouseover, etc events just related to Markers in the cluster.
+To recieve events for clusters listen to 'cluster' + 'eventIWant', ex: 'clusterclick', 'clustermouseover'.
+
+Set your callback up as follows to handle both cases:
+
+```javascript
+markers.on('click', function (a) {
+ console.log('marker ' + a.layer);
+});
+
+markers.on('clusterclick', function (a) {
+ console.log('cluster ' + a.layer.getAllChildMarkers().length);
+});
+```
+
+## Methods
+
+### Getting the bounds of a cluster
+When you recieve an event from a cluster you can query it for the bounds.
+See [example/marker-clustering-convexhull.html](http://leaflet.github.com/Leaflet.markercluster/example/marker-clustering-convexhull.html) for a working example.
+```javascript
+markers.on('clusterclick', function (a) {
+ map.addLayer(new L.Polygon(a.layer.getConvexHull()));
+});
+```
+
+### Zooming to the bounds of a cluster
+When you recieve an event from a cluster you can zoom to its bounds in one easy step.
+See [marker-clustering-zoomtobounds.html](http://leaflet.github.com/Leaflet.markercluster/example/marker-clustering-zoomtobounds.html) for a working example.
+```javascript
+markers.on('clusterclick', function (a) {
+ a.layer.zoomToBounds();
+});
+```
+
+### Adding and removing Markers
+addLayer, removeLayer and clearLayers are supported and they should work for most uses.
+
+### Bulk adding and removing Markers
+addLayers and removeLayers are bulk methods for adding and removing markers and should be favoured over the single versions when doing bulk addition/removal of markers. Each takes an array of markers
+
+If you are removing a lot of markers it will almost definitely be better to call clearLayers then call addLayers to add the markers you don't want to remove back in. See [#59](https://github.com/Leaflet/Leaflet.markercluster/issues/59#issuecomment-9320628) for details.
+
+### Other Methods
+````
+hasLayer(layer): Returns true if the given layer (marker) is in the MarkerClusterGroup
+zoomToShowLayer(layer, callback): Zooms to show the given marker (spidifying if required), calls the callback when the marker is visible on the map
+addLayers(layerArray): Adds the markers in the given array from the MarkerClusterGroup in an efficent bulk method.
+removeLayers(layerArray): Removes the markers in the given array from the MarkerClusterGroup in an efficent bulk method.
+````
+
+## Handling LOTS of markers
+The Clusterer can handle 10000 or even 50000 markers (in chrome). IE9 has some issues with 50000.
+[realworld 10000 example](http://leaflet.github.com/Leaflet.markercluster/example/marker-clustering-realworld.10000.html)
+[realworld 50000 example](http://leaflet.github.com/Leaflet.markercluster/example/marker-clustering-realworld.50000.html)
+Performance optimizations could be done so these are handled more gracefully (Running the initial clustering over multiple JS calls rather than locking the browser for a long time)
+
+### License
+
+Leaflet.markercluster is free software, and may be redistributed under the MIT-LICENSE.
diff --git a/chimere/static/leaflet-markercluster/leaflet.markercluster.js b/chimere/static/leaflet-markercluster/leaflet.markercluster.js
new file mode 100644
index 0000000..74c25c3
--- /dev/null
+++ b/chimere/static/leaflet-markercluster/leaflet.markercluster.js
@@ -0,0 +1,6 @@
+/*
+ Copyright (c) 2012, Smartrak, David Leaver
+ Leaflet.markercluster is an open-source JavaScript library for Marker Clustering on leaflet powered maps.
+ https://github.com/danzel/Leaflet.markercluster
+*/
+(function(e,t){L.MarkerClusterGroup=L.FeatureGroup.extend({options:{maxClusterRadius:80,iconCreateFunction:null,spiderfyOnMaxZoom:!0,showCoverageOnHover:!0,zoomToBoundsOnClick:!0,singleMarkerMode:!1,disableClusteringAtZoom:null,removeOutsideVisibleBounds:!0,animateAddingMarkers:!1,spiderfyDistanceMultiplier:1,polygonOptions:{}},initialize:function(e){L.Util.setOptions(this,e),this.options.iconCreateFunction||(this.options.iconCreateFunction=this._defaultIconCreateFunction),L.FeatureGroup.prototype.initialize.call(this,[]),this._inZoomAnimation=0,this._needsClustering=[],this._currentShownBounds=null},addLayer:function(e){if(e instanceof L.LayerGroup){var t=[];for(var n in e._layers)e._layers.hasOwnProperty(n)&&t.push(e._layers[n]);return this.addLayers(t)}if(!this._map)return this._needsClustering.push(e),this;if(this.hasLayer(e))return this;this._unspiderfy&&this._unspiderfy(),this._addLayer(e,this._maxZoom);var r=e,i=this._map.getZoom();if(e.__parent)while(r.__parent._zoom>=i)r=r.__parent;return this._currentShownBounds.contains(r.getLatLng())&&(this.options.animateAddingMarkers?this._animationAddLayer(e,r):this._animationAddLayerNonAnimated(e,r)),this},removeLayer:function(e){return this._map?e.__parent?(this._unspiderfy&&(this._unspiderfy(),this._unspiderfyLayer(e)),this._removeLayer(e,!0),e._icon&&(L.FeatureGroup.prototype.removeLayer.call(this,e),e.setOpacity(1)),this):this:(this._arraySplice(this._needsClustering,e),this)},addLayers:function(e){var t,n,r;if(!this._map)return this._needsClustering=this._needsClustering.concat(e),this;for(t=0,n=e.length;t<n;t++){r=e[t];if(this.hasLayer(r))continue;this._addLayer(r,this._maxZoom);if(r.__parent&&r.__parent.getChildCount()===2){var i=r.__parent.getAllChildMarkers(),s=i[0]===r?i[1]:i[0];L.FeatureGroup.prototype.removeLayer.call(this,s)}}for(t in this._layers)this._layers.hasOwnProperty(t)&&(r=this._layers[t],r instanceof L.MarkerCluster&&r._iconNeedsUpdate&&r._updateIcon());return this._topClusterLevel._recursivelyAddChildrenToMap(null,this._zoom,this._currentShownBounds),this},removeLayers:function(e){var t,n,r;if(!this._map){for(t=0,n=e.length;t<n;t++)this._arraySplice(this._needsClustering,e[t]);return this}for(t=0,n=e.length;t<n;t++){r=e[t];if(!r.__parent)continue;this._removeLayer(r,!0,!0),r._icon&&(L.FeatureGroup.prototype.removeLayer.call(this,r),r.setOpacity(1))}this._topClusterLevel._recursivelyAddChildrenToMap(null,this._zoom,this._currentShownBounds);for(t in this._layers)this._layers.hasOwnProperty(t)&&(r=this._layers[t],r instanceof L.MarkerCluster&&r._updateIcon());return this},clearLayers:function(){this._map||(this._needsClustering=[],delete this._gridClusters,delete this._gridUnclustered),this._unspiderfy&&this._unspiderfy();for(var e in this._layers)this._layers.hasOwnProperty(e)&&L.FeatureGroup.prototype.removeLayer.call(this,this._layers[e]);return this.eachLayer(function(e){delete e.__parent}),this._map&&this._generateInitialClusters(),this},getBounds:function(){var e=new L.LatLngBounds;if(this._topClusterLevel)e.extend(this._topClusterLevel._bounds);else for(var t=this._needsClustering.length-1;t>=0;t--)e.extend(this._needsClustering[t].getLatLng());return e},eachLayer:function(e,t){var n=this._needsClustering.slice(),r;this._topClusterLevel&&this._topClusterLevel.getAllChildMarkers(n);for(r=n.length-1;r>=0;r--)e.call(t,n[r])},hasLayer:function(e){if(this._needsClustering.length>0){var t=this._needsClustering;for(var n=t.length-1;n>=0;n--)if(t[n]===e)return!0}return!!e.__parent&&e.__parent._group===this},zoomToShowLayer:function(e,t){var n=function(){if((e._icon||e.__parent._icon)&&!this._inZoomAnimation){this._map.off("moveend",n,this),this.off("animationend",n,this);if(e._icon)t();else if(e.__parent._icon){var r=function(){this.off("spiderfied",r,this),t()};this.on("spiderfied",r,this),e.__parent.spiderfy()}}};e._icon?t():e.__parent._zoom<this._map.getZoom()?(this._map.on("moveend",n,this),e._icon||this._map.panTo(e.getLatLng())):(this._map.on("moveend",n,this),this.on("animationend",n,this),this._map.setView(e.getLatLng(),e.__parent._zoom+1),e.__parent.zoomToBounds())},onAdd:function(e){this._map=e,this._gridClusters||this._generateInitialClusters();for(var t=0,n=this._needsClustering.length;t<n;t++){var r=this._needsClustering[t];if(r.__parent)continue;this._addLayer(r,this._maxZoom)}this._needsClustering=[],this._map.on("zoomend",this._zoomEnd,this),this._map.on("moveend",this._moveEnd,this),this._spiderfierOnAdd&&this._spiderfierOnAdd(),this._bindEvents(),this._zoom=this._map.getZoom(),this._currentShownBounds=this._getExpandedVisibleBounds(),this._topClusterLevel._recursivelyAddChildrenToMap(null,this._zoom,this._currentShownBounds)},onRemove:function(e){this._map.off("zoomend",this._zoomEnd,this),this._map.off("moveend",this._moveEnd,this),this._unbindEvents(),this._map._mapPane.className=this._map._mapPane.className.replace(" leaflet-cluster-anim",""),this._spiderfierOnRemove&&this._spiderfierOnRemove();for(var t in this._layers)this._layers.hasOwnProperty(t)&&L.FeatureGroup.prototype.removeLayer.call(this,this._layers[t]);this._map=null},_arraySplice:function(e,t){for(var n=e.length-1;n>=0;n--)if(e[n]===t){e.splice(n,1);return}},_removeLayer:function(e,t,n){var r=this._gridClusters,i=this._gridUnclustered,s=this._map;if(t)for(var o=this._maxZoom;o>=0;o--)if(!i[o].removeObject(e,s.project(e.getLatLng(),o)))break;var u=e.__parent,a=u._markers,f;this._arraySplice(a,e);while(u){u._childCount--;if(u._zoom<0)break;t&&u._childCount<=1?(f=u._markers[0]===e?u._markers[1]:u._markers[0],r[u._zoom].removeObject(u,s.project(u._cLatLng,u._zoom)),i[u._zoom].addObject(f,s.project(f.getLatLng(),u._zoom)),this._arraySplice(u.__parent._childClusters,u),u.__parent._markers.push(f),f.__parent=u.__parent,u._icon&&(L.FeatureGroup.prototype.removeLayer.call(this,u),n||L.FeatureGroup.prototype.addLayer.call(this,f))):(u._recalculateBounds(),(!n||!u._icon)&&u._updateIcon()),u=u.__parent}delete e.__parent},_propagateEvent:function(e){e.target instanceof L.MarkerCluster&&(e.type="cluster"+e.type),L.FeatureGroup.prototype._propagateEvent.call(this,e)},_defaultIconCreateFunction:function(e){var t=e.getChildCount(),n=" marker-cluster-";return t<10?n+="small":t<100?n+="medium":n+="large",new L.DivIcon({html:"<div><span>"+t+"</span></div>",className:"marker-cluster"+n,iconSize:new L.Point(40,40)})},_bindEvents:function(){var e=null,t=this._map,n=this.options.spiderfyOnMaxZoom,r=this.options.showCoverageOnHover,i=this.options.zoomToBoundsOnClick;(n||i)&&this.on("clusterclick",function(e){t.getMaxZoom()===t.getZoom()?n&&e.layer.spiderfy():i&&e.layer.zoomToBounds()},this),r&&(this.on("clustermouseover",function(n){if(this._inZoomAnimation)return;e&&t.removeLayer(e),n.layer.getChildCount()>2&&n.layer!==this._spiderfied&&(e=new L.Polygon(n.layer.getConvexHull(),this.options.polygonOptions),t.addLayer(e))},this),this.on("clustermouseout",function(){e&&(t.removeLayer(e),e=null)},this),t.on("zoomend",function(){e&&(t.removeLayer(e),e=null)},this),t.on("layerremove",function(n){e&&n.layer===this&&(t.removeLayer(e),e=null)},this))},_unbindEvents:function(){var e=this.options.spiderfyOnMaxZoom,t=this.options.showCoverageOnHover,n=this.options.zoomToBoundsOnClick,r=this._map;(e||n)&&this.off("clusterclick",null,this),t&&(this.off("clustermouseover",null,this),this.off("clustermouseout",null,this),r.off("zoomend",null,this),r.off("layerremove",null,this))},_zoomEnd:function(){if(!this._map)return;this._mergeSplitClusters(),this._zoom=this._map._zoom,this._currentShownBounds=this._getExpandedVisibleBounds()},_moveEnd:function(){if(this._inZoomAnimation)return;var e=this._getExpandedVisibleBounds();this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds,this._zoom,e),this._topClusterLevel._recursivelyAddChildrenToMap(null,this._zoom,e),this._currentShownBounds=e;return},_generateInitialClusters:function(){var e=this._map.getMaxZoom(),t=this.options.maxClusterRadius;this.options.disableClusteringAtZoom&&(e=this.options.disableClusteringAtZoom-1),this._maxZoom=e,this._gridClusters={},this._gridUnclustered={};for(var n=e;n>=0;n--)this._gridClusters[n]=new L.DistanceGrid(t),this._gridUnclustered[n]=new L.DistanceGrid(t);this._topClusterLevel=new L.MarkerCluster(this,-1)},_addLayer:function(e,t){var n=this._gridClusters,r=this._gridUnclustered,i,s;this.options.singleMarkerMode&&(e.options.icon=this.options.iconCreateFunction({getChildCount:function(){return 1},getAllChildMarkers:function(){return[e]}}));for(;t>=0;t--){i=this._map.project(e.getLatLng(),t);var o=n[t].getNearObject(i);if(o){o._addChild(e),e.__parent=o;return}o=r[t].getNearObject(i);if(o){var u=o.__parent;u&&this._removeLayer(o,!1);var a=new L.MarkerCluster(this,t,o,e);n[t].addObject(a,this._map.project(a._cLatLng,t)),o.__parent=a,e.__parent=a;var f=a;for(s=t-1;s>u._zoom;s--)f=new L.MarkerCluster(this,s,f),n[s].addObject(f,this._map.project(o.getLatLng(),s));u._addChild(f);for(s=t;s>=0;s--)if(!r[s].removeObject(o,this._map.project(o.getLatLng(),s)))break;return}r[t].addObject(e,i)}this._topClusterLevel._addChild(e),e.__parent=this._topClusterLevel;return},_mergeSplitClusters:function(){this._zoom<this._map._zoom?(this._animationStart(),this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds,this._zoom,this._getExpandedVisibleBounds()),this._animationZoomIn(this._zoom,this._map._zoom)):this._zoom>this._map._zoom?(this._animationStart(),this._animationZoomOut(this._zoom,this._map._zoom)):this._moveEnd()},_getExpandedVisibleBounds:function(){if(!this.options.removeOutsideVisibleBounds)return this.getBounds();var e=this._map,t=e.getBounds(),n=t._southWest,r=t._northEast,i=L.Browser.mobile?0:Math.abs(n.lat-r.lat),s=L.Browser.mobile?0:Math.abs(n.lng-r.lng);return new L.LatLngBounds(new L.LatLng(n.lat-i,n.lng-s,!0),new L.LatLng(r.lat+i,r.lng+s,!0))},_animationAddLayerNonAnimated:function(e,t){if(t===e)L.FeatureGroup.prototype.addLayer.call(this,e);else if(t._childCount===2){t._addToMap();var n=t.getAllChildMarkers();L.FeatureGroup.prototype.removeLayer.call(this,n[0]),L.FeatureGroup.prototype.removeLayer.call(this,n[1])}else t._updateIcon()}}),L.MarkerClusterGroup.include(L.DomUtil.TRANSITION?{_animationStart:function(){this._map._mapPane.className+=" leaflet-cluster-anim",this._inZoomAnimation++},_animationEnd:function(){this._map&&(this._map._mapPane.className=this._map._mapPane.className.replace(" leaflet-cluster-anim","")),this._inZoomAnimation--,this.fire("animationend")},_animationZoomIn:function(e,t){var n=this,r=this._getExpandedVisibleBounds(),i;this._topClusterLevel._recursively(r,e,0,function(s){var o=s._latlng,u=s._markers,a;s._isSingleParent()&&e+1===t?(L.FeatureGroup.prototype.removeLayer.call(n,s),s._recursivelyAddChildrenToMap(null,t,r)):(s.setOpacity(0),s._recursivelyAddChildrenToMap(o,t,r));for(i=u.length-1;i>=0;i--)a=u[i],r.contains(a._latlng)||L.FeatureGroup.prototype.removeLayer.call(n,a)}),this._forceLayout();var s,o;n._topClusterLevel._recursivelyBecomeVisible(r,t);for(s in n._layers)n._layers.hasOwnProperty(s)&&(o=n._layers[s],!(o instanceof L.MarkerCluster)&&o._icon&&o.setOpacity(1));n._topClusterLevel._recursively(r,e,t,function(e){e._recursivelyRestoreChildPositions(t)}),setTimeout(function(){n._topClusterLevel._recursively(r,e,0,function(e){L.FeatureGroup.prototype.removeLayer.call(n,e),e.setOpacity(1)}),n._animationEnd()},200)},_animationZoomOut:function(e,t){this._animationZoomOutSingle(this._topClusterLevel,e-1,t),this._topClusterLevel._recursivelyAddChildrenToMap(null,t,this._getExpandedVisibleBounds()),this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds,e,this._getExpandedVisibleBounds())},_animationZoomOutSingle:function(e,t,n){var r=this._getExpandedVisibleBounds();e._recursivelyAnimateChildrenInAndAddSelfToMap(r,t+1,n);var i=this;this._forceLayout(),e._recursivelyBecomeVisible(r,n),setTimeout(function(){if(e._childCount===1){var s=e._markers[0];s.setLatLng(s.getLatLng()),s.setOpacity(1);return}e._recursively(r,n,0,function(e){e._recursivelyRemoveChildrenFromMap(r,t+1)}),i._animationEnd()},200)},_animationAddLayer:function(e,t){var n=this;L.FeatureGroup.prototype.addLayer.call(this,e),t!==e&&(t._childCount>2?(t._updateIcon(),this._forceLayout(),this._animationStart(),e._setPos(this._map.latLngToLayerPoint(t.getLatLng())),e.setOpacity(0),setTimeout(function(){L.FeatureGroup.prototype.removeLayer.call(n,e),e.setOpacity(1),n._animationEnd()},200)):(this._forceLayout(),n._animationStart(),n._animationZoomOutSingle(t,this._map.getMaxZoom(),this._map.getZoom())))},_forceLayout:function(){L.Util.falseFn(document.body.offsetWidth)}}:{_animationStart:function(){},_animationZoomIn:function(e,t){this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds,e),this._topClusterLevel._recursivelyAddChildrenToMap(null,t,this._getExpandedVisibleBounds())},_animationZoomOut:function(e,t){this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds,e),this._topClusterLevel._recursivelyAddChildrenToMap(null,t,this._getExpandedVisibleBounds())},_animationAddLayer:function(e,t){this._animationAddLayerNonAnimated(e,t)}}),L.MarkerCluster=L.Marker.extend({initialize:function(e,t,n,r){L.Marker.prototype.initialize.call(this,n?n._cLatLng||n.getLatLng():new L.LatLng(0,0),{icon:this}),this._group=e,this._zoom=t,this._markers=[],this._childClusters=[],this._childCount=0,this._iconNeedsUpdate=!0,this._bounds=new L.LatLngBounds,n&&this._addChild(n),r&&this._addChild(r)},getAllChildMarkers:function(e){e=e||[];for(var t=this._childClusters.length-1;t>=0;t--)this._childClusters[t].getAllChildMarkers(e);for(var n=this._markers.length-1;n>=0;n--)e.push(this._markers[n]);return e},getChildCount:function(){return this._childCount},zoomToBounds:function(){this._group._map.fitBounds(this._bounds)},_updateIcon:function(){this._iconNeedsUpdate=!0,this._icon&&this.setIcon(this)},createIcon:function(){return this._iconNeedsUpdate&&(this._iconObj=this._group.options.iconCreateFunction(this),this._iconNeedsUpdate=!1),this._iconObj.createIcon()},createShadow:function(){return this._iconObj.createShadow()},_addChild:function(e,t){this._iconNeedsUpdate=!0,this._expandBounds(e),e instanceof L.MarkerCluster?(t||(this._childClusters.push(e),e.__parent=this),this._childCount+=e._childCount):(t||this._markers.push(e),this._childCount++),this.__parent&&this.__parent._addChild(e,!0)},_expandBounds:function(e){var t,n=e._wLatLng||e._latlng;e instanceof L.MarkerCluster?(this._bounds.extend(e._bounds),t=e._childCount):(this._bounds.extend(n),t=1),this._cLatLng||(this._cLatLng=e._cLatLng||n);var r=this._childCount+t;this._wLatLng?(this._wLatLng.lat=(n.lat*t+this._wLatLng.lat*this._childCount)/r,this._wLatLng.lng=(n.lng*t+this._wLatLng.lng*this._childCount)/r):this._latlng=this._wLatLng=new L.LatLng(n.lat,n.lng)},_addToMap:function(e){e&&(this._backupLatlng=this._latlng,this.setLatLng(e)),L.FeatureGroup.prototype.addLayer.call(this._group,this)},_recursivelyAnimateChildrenIn:function(e,t,n){this._recursively(e,0,n-1,function(e){var n=e._markers,r,i;for(r=n.length-1;r>=0;r--)i=n[r],i._icon&&(i._setPos(t),i.setOpacity(0))},function(e){var n=e._childClusters,r,i;for(r=n.length-1;r>=0;r--)i=n[r],i._icon&&(i._setPos(t),i.setOpacity(0))})},_recursivelyAnimateChildrenInAndAddSelfToMap:function(e,t,n){this._recursively(e,n,0,function(r){r._recursivelyAnimateChildrenIn(e,r._group._map.latLngToLayerPoint(r.getLatLng()).round(),t),r._isSingleParent()&&t-1===n?(r.setOpacity(1),r._recursivelyRemoveChildrenFromMap(e,t)):r.setOpacity(0),r._addToMap()})},_recursivelyBecomeVisible:function(e,t){this._recursively(e,0,t,null,function(e){e.setOpacity(1)})},_recursivelyAddChildrenToMap:function(e,t,n){this._recursively(n,-1,t,function(r){if(t===r._zoom)return;for(var i=r._markers.length-1;i>=0;i--){var s=r._markers[i];if(!n.contains(s._latlng))continue;e&&(s._backupLatlng=s.getLatLng(),s.setLatLng(e),s.setOpacity(0)),L.FeatureGroup.prototype.addLayer.call(r._group,s)}},function(t){t._addToMap(e)})},_recursivelyRestoreChildPositions:function(e){for(var t=this._markers.length-1;t>=0;t--){var n=this._markers[t];n._backupLatlng&&(n.setLatLng(n._backupLatlng),delete n._backupLatlng)}if(e-1===this._zoom)for(var r=this._childClusters.length-1;r>=0;r--)this._childClusters[r]._restorePosition();else for(var i=this._childClusters.length-1;i>=0;i--)this._childClusters[i]._recursivelyRestoreChildPositions(e)},_restorePosition:function(){this._backupLatlng&&(this.setLatLng(this._backupLatlng),delete this._backupLatlng)},_recursivelyRemoveChildrenFromMap:function(e,t,n){var r,i;this._recursively(e,-1,t-1,function(e){for(i=e._markers.length-1;i>=0;i--){r=e._markers[i];if(!n||!n.contains(r._latlng))L.FeatureGroup.prototype.removeLayer.call(e._group,r),r.setOpacity(1)}},function(e){for(i=e._childClusters.length-1;i>=0;i--){r=e._childClusters[i];if(!n||!n.contains(r._latlng))L.FeatureGroup.prototype.removeLayer.call(e._group,r),r.setOpacity(1)}})},_recursively:function(e,t,n,r,i){var s=this._childClusters,o=this._zoom,u,a;if(t>o)for(u=s.length-1;u>=0;u--)a=s[u],e.intersects(a._bounds)&&a._recursively(e,t,n,r,i);else{r&&r(this),i&&this._zoom===n&&i(this);if(n>o)for(u=s.length-1;u>=0;u--)a=s[u],e.intersects(a._bounds)&&a._recursively(e,t,n,r,i)}},_recalculateBounds:function(){var e=this._markers,t=this._childClusters,n;this._bounds=new L.LatLngBounds,delete this._wLatLng;for(n=e.length-1;n>=0;n--)this._expandBounds(e[n]);for(n=t.length-1;n>=0;n--)this._expandBounds(t[n])},_isSingleParent:function(){return this._childClusters.length>0&&this._childClusters[0]._childCount===this._childCount}}),L.DistanceGrid=function(e){this._cellSize=e,this._sqCellSize=e*e,this._grid={},this._objectPoint={}},L.DistanceGrid.prototype={addObject:function(e,t){var n=this._getCoord(t.x),r=this._getCoord(t.y),i=this._grid,s=i[r]=i[r]||{},o=s[n]=s[n]||[],u=L.Util.stamp(e);this._objectPoint[u]=t,o.push(e)},updateObject:function(e,t){this.removeObject(e),this.addObject(e,t)},removeObject:function(e,t){var n=this._getCoord(t.x),r=this._getCoord(t.y),i=this._grid,s=i[r]=i[r]||{},o=s[n]=s[n]||[],u,a;delete this._objectPoint[L.Util.stamp(e)];for(u=0,a=o.length;u<a;u++)if(o[u]===e)return o.splice(u,1),a===1&&delete s[n],!0},eachObject:function(e,t){var n,r,i,s,o,u,a,f=this._grid;for(n in f)if(f.hasOwnProperty(n)){o=f[n];for(r in o)if(o.hasOwnProperty(r)){u=o[r];for(i=0,s=u.length;i<s;i++)a=e.call(t,u[i]),a&&(i--,s--)}}},getNearObject:function(e){var t=this._getCoord(e.x),n=this._getCoord(e.y),r,i,s,o,u,a,f,l,c=this._objectPoint,h=this._sqCellSize,p=null;for(r=n-1;r<=n+1;r++){o=this._grid[r];if(o)for(i=t-1;i<=t+1;i++){u=o[i];if(u)for(s=0,a=u.length;s<a;s++)f=u[s],l=this._sqDist(c[L.Util.stamp(f)],e),l<h&&(h=l,p=f)}}return p},_getCoord:function(e){return Math.floor(e/this._cellSize)},_sqDist:function(e,t){var n=t.x-e.x,r=t.y-e.y;return n*n+r*r}},function(){L.QuickHull={getDistant:function(e,t){var n=t[1].lat-t[0].lat,r=t[0].lng-t[1].lng;return r*(e.lat-t[0].lat)+n*(e.lng-t[0].lng)},findMostDistantPointFromBaseLine:function(e,t){var n=0,r=null,i=[],s,o,u;for(s=t.length-1;s>=0;s--){o=t[s],u=this.getDistant(o,e);if(!(u>0))continue;i.push(o),u>n&&(n=u,r=o)}return{maxPoint:r,newPoints:i}},buildConvexHull:function(e,t){var n=[],r=this.findMostDistantPointFromBaseLine(e,t);return r.maxPoint?(n=n.concat(this.buildConvexHull([e[0],r.maxPoint],r.newPoints)),n=n.concat(this.buildConvexHull([r.maxPoint,e[1]],r.newPoints)),n):[e]},getConvexHull:function(e){var t=!1,n=!1,r=null,i=null,s;for(s=e.length-1;s>=0;s--){var o=e[s];if(t===!1||o.lat>t)r=o,t=o.lat;if(n===!1||o.lat<n)i=o,n=o.lat}var u=[].concat(this.buildConvexHull([i,r],e),this.buildConvexHull([r,i],e));return u}}}(),L.MarkerCluster.include({getConvexHull:function(){var e=this.getAllChildMarkers(),t=[],n=[],r,i,s;for(s=e.length-1;s>=0;s--)i=e[s].getLatLng(),t.push(i);r=L.QuickHull.getConvexHull(t);for(s=r.length-1;s>=0;s--)n.push(r[s][0]);return n}}),L.MarkerCluster.include({_2PI:Math.PI*2,_circleFootSeparation:25,_circleStartAngle:Math.PI/6,_spiralFootSeparation:28,_spiralLengthStart:11,_spiralLengthFactor:5,_circleSpiralSwitchover:9,spiderfy:function(){if(this._group._spiderfied===this||this._group._inZoomAnimation)return;var e=this.getAllChildMarkers(),t=this._group,n=t._map,r=n.latLngToLayerPoint(this._latlng),i;this._group._unspiderfy(),this._group._spiderfied=this,e.length>=this._circleSpiralSwitchover?i=this._generatePointsSpiral(e.length,r):(r.y+=10,i=this._generatePointsCircle(e.length,r)),this._animationSpiderfy(e,i)},unspiderfy:function(e){if(this._group._inZoomAnimation)return;this._animationUnspiderfy(e),this._group._spiderfied=null},_generatePointsCircle:function(e,t){var n=this._group.options.spiderfyDistanceMultiplier*this._circleFootSeparation*(2+e),r=n/this._2PI,i=this._2PI/e,s=[],o,u;s.length=e;for(o=e-1;o>=0;o--)u=this._circleStartAngle+o*i,s[o]=(new L.Point(t.x+r*Math.cos(u),t.y+r*Math.sin(u)))._round();return s},_generatePointsSpiral:function(e,t){var n=this._group.options.spiderfyDistanceMultiplier*this._spiralLengthStart,r=this._group.options.spiderfyDistanceMultiplier*this._spiralFootSeparation,i=this._group.options.spiderfyDistanceMultiplier*this._spiralLengthFactor,s=0,o=[],u;o.length=e;for(u=e-1;u>=0;u--)s+=r/n+u*5e-4,o[u]=(new L.Point(t.x+n*Math.cos(s),t.y+n*Math.sin(s)))._round(),n+=this._2PI*i/s;return o}}),L.MarkerCluster.include(L.DomUtil.TRANSITION?{SVG_ANIMATION:function(){return(document.createElementNS("http://www.w3.org/2000/svg","animate")+"").indexOf("SVGAnimate")>-1}(),_animationSpiderfy:function(e,t){var n=this,r=this._group,i=r._map,s=i.latLngToLayerPoint(this._latlng),o,u,a,f;for(o=e.length-1;o>=0;o--)u=e[o],u.setZIndexOffset(1e6),u.setOpacity(0),L.FeatureGroup.prototype.addLayer.call(r,u),u._setPos(s);r._forceLayout(),r._animationStart();var l=L.Path.SVG?0:.3,c=L.Path.SVG_NS;for(o=e.length-1;o>=0;o--){f=i.layerPointToLatLng(t[o]),u=e[o],u._preSpiderfyLatlng=u._latlng,u.setLatLng(f),u.setOpacity(1),a=new L.Polyline([n._latlng,f],{weight:1.5,color:"#222",opacity:l}),i.addLayer(a),u._spiderLeg=a;if(!L.Path.SVG||!this.SVG_ANIMATION)continue;var h=a._path.getTotalLength();a._path.setAttribute("stroke-dasharray",h+","+h);var p=document.createElementNS(c,"animate");p.setAttribute("attributeName","stroke-dashoffset"),p.setAttribute("begin","indefinite"),p.setAttribute("from",h),p.setAttribute("to",0),p.setAttribute("dur",.25),a._path.appendChild(p),p.beginElement(),p=document.createElementNS(c,"animate"),p.setAttribute("attributeName","stroke-opacity"),p.setAttribute("attributeName","stroke-opacity"),p.setAttribute("begin","indefinite"),p.setAttribute("from",0),p.setAttribute("to",.5),p.setAttribute("dur",.25),a._path.appendChild(p),p.beginElement()}n.setOpacity(.3);if(L.Path.SVG){this._group._forceLayout();for(o=e.length-1;o>=0;o--)u=e[o]._spiderLeg,u.options.opacity=.5,u._path.setAttribute("stroke-opacity",.5)}setTimeout(function(){r._animationEnd(),r.fire("spiderfied")},200)},_animationUnspiderfy:function(e){var t=this._group,n=t._map,r=e?n._latLngToNewLayerPoint(this._latlng,e.zoom,e.center):n.latLngToLayerPoint(this._latlng),i=this.getAllChildMarkers(),s=L.Path.SVG&&this.SVG_ANIMATION,o,u,a;t._animationStart(),this.setOpacity(1);for(u=i.length-1;u>=0;u--){o=i[u];if(!o._preSpiderfyLatlng)continue;o.setLatLng(o._preSpiderfyLatlng),delete o._preSpiderfyLatlng,o._setPos(r),o.setOpacity(0),s&&(a=o._spiderLeg._path.childNodes[0],a.setAttribute("to",a.getAttribute("from")),a.setAttribute("from",0),a.beginElement(),a=o._spiderLeg._path.childNodes[1],a.setAttribute("from",.5),a.setAttribute("to",0),a.setAttribute("stroke-opacity",0),a.beginElement(),o._spiderLeg._path.setAttribute("stroke-opacity",0))}setTimeout(function(){var e=0;for(u=i.length-1;u>=0;u--)o=i[u],o._spiderLeg&&e++;for(u=i.length-1;u>=0;u--){o=i[u];if(!o._spiderLeg)continue;o.setOpacity(1),o.setZIndexOffset(0),e>1&&L.FeatureGroup.prototype.removeLayer.call(t,o),n.removeLayer(o._spiderLeg),delete o._spiderLeg}t._animationEnd()},200)}}:{_animationSpiderfy:function(e,t){var n=this._group,r=n._map,i,s,o,u;for(i=e.length-1;i>=0;i--)u=r.layerPointToLatLng(t[i]),s=e[i],s._preSpiderfyLatlng=s._latlng,s.setLatLng(u),s.setZIndexOffset(1e6),L.FeatureGroup.prototype.addLayer.call(n,s),o=new L.Polyline([this._latlng,u],{weight:1.5,color:"#222"}),r.addLayer(o),s._spiderLeg=o;this.setOpacity(.3),n.fire("spiderfied")},_animationUnspiderfy:function(){var e=this._group,t=e._map,n=this.getAllChildMarkers(),r,i;this.setOpacity(1);for(i=n.length-1;i>=0;i--)r=n[i],L.FeatureGroup.prototype.removeLayer.call(e,r),r.setLatLng(r._preSpiderfyLatlng),delete r._preSpiderfyLatlng,r.setZIndexOffset(0),t.removeLayer(r._spiderLeg),delete r._spiderLeg}}),L.MarkerClusterGroup.include({_spiderfied:null,_spiderfierOnAdd:function(){this._map.on("click",this._unspiderfyWrapper,this),this._map.options.zoomAnimation?this._map.on("zoomstart",this._unspiderfyZoomStart,this):this._map.on("zoomend",this._unspiderfyWrapper,this),L.Path.SVG&&!L.Browser.touch&&this._map._initPathRoot()},_spiderfierOnRemove:function(){this._map.off("click",this._unspiderfyWrapper,this),this._map.off("zoomstart",this._unspiderfyZoomStart,this),this._map.off("zoomanim",this._unspiderfyZoomAnim,this),this._unspiderfy()},_unspiderfyZoomStart:function(){if(!this._map)return;this._map.on("zoomanim",this._unspiderfyZoomAnim,this)},_unspiderfyZoomAnim:function(e){if(L.DomUtil.hasClass(this._map._mapPane,"leaflet-touching"))return;this._map.off("zoomanim",this._unspiderfyZoomAnim,this),this._unspiderfy(e)},_unspiderfyWrapper:function(){this._unspiderfy()},_unspiderfy:function(e){this._spiderfied&&this._spiderfied.unspiderfy(e)},_unspiderfyLayer:function(e){e._spiderLeg&&(L.FeatureGroup.prototype.removeLayer.call(this,e),e.setOpacity(1),e.setZIndexOffset(0),this._map.removeLayer(e._spiderLeg),delete e._spiderLeg)}})})(this); \ No newline at end of file
diff --git a/chimere/static/leaflet/LICENSE b/chimere/static/leaflet/LICENSE
new file mode 100644
index 0000000..46cd121
--- /dev/null
+++ b/chimere/static/leaflet/LICENSE
@@ -0,0 +1,23 @@
+Copyright (c) 2010-2013, Vladimir Agafonkin
+Copyright (c) 2010-2011, CloudMade
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are
+permitted provided that the following conditions are met:
+
+ 1. Redistributions of source code must retain the above copyright notice, this list of
+ conditions and the following disclaimer.
+
+ 2. Redistributions in binary form must reproduce the above copyright notice, this list
+ of conditions and the following disclaimer in the documentation and/or other materials
+ provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/chimere/static/leaflet/README.md b/chimere/static/leaflet/README.md
new file mode 100644
index 0000000..e974712
--- /dev/null
+++ b/chimere/static/leaflet/README.md
@@ -0,0 +1,28 @@
+<img src="http://leafletjs.com/docs/images/logo.png" alt="Leaflet" />
+
+Leaflet is a modern open-source JavaScript library for **mobile-friendly interactive maps**.
+It is developed by [Vladimir Agafonkin][] with a team of dedicated [contributors][].
+Weighing just about 27 KB of gzipped JS code, it has all the [features][] most developers ever need for online maps.
+
+Leaflet is designed with *simplicity*, *performance* and *usability* in mind.
+It works efficiently across all major desktop and mobile platforms out of the box,
+taking advantage of HTML5 and CSS3 on modern browsers while being accessible on older ones too.
+It can also be extended with many [plugins][],
+has a beautiful, easy to use and [well-documented][] API
+and a simple, readable [source code][] that is a joy to [contribute][] to.
+
+For more information, check out the [official website][].
+
+We're happy to meet new contributors.
+If you want to **get involved** with Leaflet development, check out the [contribution guide][contribute].
+Let's make the best open-source library for maps that can possibly exist!
+
+ [Vladimir Agafonkin]: http://agafonkin.com/en
+ [contributors]: https://github.com/Leaflet/Leaflet/graphs/contributors
+ [features]: http://leafletjs.com/features.html
+ [plugins]: http://leafletjs.com/plugins.html
+ [well-documented]: http://leafletjs.com/reference.html "Leaflet API reference"
+ [source code]: https://github.com/Leaflet/Leaflet "Leaflet GitHub repository"
+ [hosted on GitHub]: http://github.com/Leaflet/Leaflet
+ [contribute]: https://github.com/Leaflet/Leaflet/blob/master/CONTRIBUTING.md "A guide to contributing to Leaflet"
+ [official website]: http://leafletjs.com
diff --git a/chimere/static/leaflet/images/layers.png b/chimere/static/leaflet/images/layers.png
new file mode 100644
index 0000000..ef90a08
--- /dev/null
+++ b/chimere/static/leaflet/images/layers.png
Binary files differ
diff --git a/chimere/static/leaflet/images/marker-icon.png b/chimere/static/leaflet/images/marker-icon.png
new file mode 100644
index 0000000..e2e9f75
--- /dev/null
+++ b/chimere/static/leaflet/images/marker-icon.png
Binary files differ
diff --git a/chimere/static/leaflet/images/marker-icon@2x.png b/chimere/static/leaflet/images/marker-icon@2x.png
new file mode 100644
index 0000000..0015b64
--- /dev/null
+++ b/chimere/static/leaflet/images/marker-icon@2x.png
Binary files differ
diff --git a/chimere/static/leaflet/images/marker-shadow.png b/chimere/static/leaflet/images/marker-shadow.png
new file mode 100644
index 0000000..d1e773c
--- /dev/null
+++ b/chimere/static/leaflet/images/marker-shadow.png
Binary files differ
diff --git a/chimere/static/leaflet/leaflet-src.js b/chimere/static/leaflet/leaflet-src.js
new file mode 100644
index 0000000..13f82bb
--- /dev/null
+++ b/chimere/static/leaflet/leaflet-src.js
@@ -0,0 +1,8338 @@
+/*
+ Leaflet, a JavaScript library for mobile-friendly interactive maps. http://leafletjs.com
+ (c) 2010-2013, Vladimir Agafonkin, CloudMade
+*/
+(function (window, document, undefined) {/*
+ * The L namespace contains all Leaflet classes and functions.
+ * This code allows you to handle any possible namespace conflicts.
+ */
+
+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.5.1';
+
+
+/*
+ * L.Util contains various utility functions used throughout Leaflet code.
+ */
+
+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, existingUrl) {
+ var params = [];
+ for (var i in obj) {
+ if (obj.hasOwnProperty(i)) {
+ params.push(i + '=' + obj[i]);
+ }
+ }
+ return ((!existingUrl || existingUrl.indexOf('?') === -1) ? '?' : '&') + 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;
+ });
+ },
+
+ isArray: function (obj) {
+ return (Object.prototype.toString.call(obj) === '[object Array]');
+ },
+
+ 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;
+
+
+/*
+ * L.Class powers the OOP facilities of the library.
+ * Thanks to John Resig and Dean Edwards for inspiration!
+ */
+
+L.Class = function () {};
+
+L.Class.extend = function (props) {
+
+ // extended class with the new prototype
+ var NewClass = function () {
+
+ // call the constructor
+ if (this.initialize) {
+ this.initialize.apply(this, arguments);
+ }
+
+ // call all constructor hooks
+ if (this._initHooks) {
+ this.callInitHooks();
+ }
+ };
+
+ // 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);
+
+ proto._initHooks = [];
+
+ var parent = this;
+ // add method for calling all hooks
+ proto.callInitHooks = function () {
+
+ if (this._initHooksCalled) { return; }
+
+ if (parent.prototype.callInitHooks) {
+ parent.prototype.callInitHooks.call(this);
+ }
+
+ this._initHooksCalled = true;
+
+ for (var i = 0, len = proto._initHooks.length; i < len; i++) {
+ proto._initHooks[i].call(this);
+ }
+ };
+
+ return NewClass;
+};
+
+
+// method for adding properties to prototype
+L.Class.include = function (props) {
+ L.extend(this.prototype, props);
+};
+
+// merge new default options to the Class
+L.Class.mergeOptions = function (options) {
+ L.extend(this.prototype.options, options);
+};
+
+// add a constructor hook
+L.Class.addInitHook = function (fn) { // (Function) || (String, args...)
+ var args = Array.prototype.slice.call(arguments, 1);
+
+ var init = typeof fn === 'function' ? fn : function () {
+ this[fn].apply(this, args);
+ };
+
+ this.prototype._initHooks = this.prototype._initHooks || [];
+ this.prototype._initHooks.push(init);
+};
+
+
+/*
+ * L.Mixin.Events is used to add 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;
+
+
+/*
+ * L.Browser handles different browser and feature detections for internal Leaflet use.
+ */
+
+(function () {
+
+ var ie = !!window.ActiveXObject,
+ ie6 = ie && !window.XMLHttpRequest,
+ ie7 = ie && !document.querySelector,
+
+ // terrible browser detection to work around Safari / iOS / Android browser bugs
+ 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)') &&
+ 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 = {
+ ie: ie,
+ 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);
+ },
+
+ equals: function (point) {
+ return point.x === this.x &&
+ point.y === this.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 (L.Util.isArray(x)) {
+ 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 = 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]);
+ }
+};
+
+L.Bounds.prototype = {
+ // 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);
+ },
+
+ getSize: function () {
+ return this.max.subtract(this.min);
+ },
+
+ 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 = function (a, b, c, d) {
+ this._a = a;
+ this._b = b;
+ this._c = c;
+ this._d = d;
+};
+
+L.Transformation.prototype = {
+ transform: function (point, scale) { // (Point, Number) -> Point
+ return this._transform(point.clone(), scale);
+ },
+
+ // destructive transform (faster)
+ _transform: function (point, scale) {
+ 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, scale) {
+ 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;
+
+ //add borders
+ top += parseInt(L.DomUtil.getStyle(el, "borderTopWidth"), 10) || 0;
+ left += parseInt(L.DomUtil.getStyle(el, "borderLeftWidth"), 10) || 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 || null;
+ 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']);
+
+// webkitTransition comes first because some browser versions that drop vendor prefix don't do
+// the same for the transitionend event, in particular the Android 4.1 stock browser
+
+L.DomUtil.TRANSITION = L.DomUtil.testProp(
+ ['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']);
+
+L.DomUtil.TRANSITION_END =
+ L.DomUtil.TRANSITION === 'webkitTransition' || L.DomUtil.TRANSITION === 'OTransition' ?
+ L.DomUtil.TRANSITION + 'End' : 'transitionend';
+
+
+/*
+ * L.LatLng represents a geographical point with latitude and longitude coordinates.
+ */
+
+L.LatLng = function (rawLat, rawLng) { // (Number, Number)
+ var lat = parseFloat(rawLat),
+ lng = parseFloat(rawLng);
+
+ if (isNaN(lat) || isNaN(lng)) {
+ throw new Error('Invalid LatLng object: (' + rawLat + ', ' + rawLng + ')');
+ }
+
+ 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) { // (Number) -> 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
+ // TODO move to projection code, LatLng shouldn't know about Earth
+ 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));
+ },
+
+ wrap: function (a, b) { // (Number, Number) -> LatLng
+ var lng = this.lng;
+
+ a = a || -180;
+ b = b || 180;
+
+ lng = (lng + b) % (b - a) + (lng < a || lng === b ? b : a);
+
+ return new L.LatLng(this.lat, lng);
+ }
+};
+
+L.latLng = function (a, b) { // (LatLng) or ([Number, Number]) or (Number, Number)
+ if (a instanceof L.LatLng) {
+ return a;
+ }
+ if (L.Util.isArray(a)) {
+ return new L.LatLng(a[0], a[1]);
+ }
+ if (isNaN(a)) {
+ return a;
+ }
+ return new L.LatLng(a, b);
+};
+
+
+
+/*
+ * L.LatLngBounds represents a rectangular area on the map in geographical coordinates.
+ */
+
+L.LatLngBounds = 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]);
+ }
+};
+
+L.LatLngBounds.prototype = {
+ // extend the bounds to contain the given point or bounds
+ extend: function (obj) { // (LatLng) or (LatLngBounds)
+ if (typeof obj[0] === 'number' || typeof obj[0] === 'string' || 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);
+ this._northEast = new L.LatLng(obj.lat, obj.lng);
+ } 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);
+ },
+
+ getSouthEast: function () {
+ return new L.LatLng(this._southWest.lat, this._northEast.lng);
+ },
+
+ 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 = {};
+
+
+/*
+ * Spherical Mercator is the most popular map projection, used by EPSG:3857 CRS used by default.
+ */
+
+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;
+
+ return new L.LatLng(lat, lng);
+ }
+};
+
+
+/*
+ * Simple equirectangular (Plate Carree) projection, used by CRS like EPSG:4326 and Simple.
+ */
+
+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);
+ }
+};
+
+
+/*
+ * L.CRS is a base object for all defined CRS (Coordinate Reference Systems) in Leaflet.
+ */
+
+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);
+ }
+};
+
+
+/*
+ * A simple CRS that can be used for flat non-Earth maps like panoramas or game maps.
+ */
+
+L.CRS.Simple = L.extend({}, L.CRS, {
+ projection: L.Projection.LonLat,
+ transformation: new L.Transformation(1, 0, -1, 0),
+
+ scale: function (zoom) {
+ return Math.pow(2, zoom);
+ }
+});
+
+
+/*
+ * L.CRS.EPSG3857 (Spherical Mercator) is the most common CRS for web mapping
+ * and is used by Leaflet by default.
+ */
+
+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 is a CRS popular among advanced GIS specialists.
+ */
+
+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.callInitHooks();
+ 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) || !isNaN(layer.options.minZoom))) {
+ this._zoomBoundLayers[id] = layer;
+ this._updateZoomLevels();
+ }
+
+ // 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];
+ if (this._zoomBoundLayers[id]) {
+ delete this._zoomBoundLayers[id];
+ this._updateZoomLevels();
+ }
+
+ // 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;
+
+ 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);
+ },
+
+ _initLayers: function (layers) {
+ layers = layers ? (L.Util.isArray(layers) ? layers : [layers]) : [];
+
+ this._layers = {};
+ this._zoomBoundLayers = {};
+ 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));
+ },
+
+ _updateZoomLevels: function () {
+ var i,
+ minZoom = Infinity,
+ maxZoom = -Infinity;
+
+ for (i in this._zoomBoundLayers) {
+ if (this._zoomBoundLayers.hasOwnProperty(i)) {
+ var layer = this._zoomBoundLayers[i];
+ if (!isNaN(layer.options.minZoom)) {
+ minZoom = Math.min(minZoom, layer.options.minZoom);
+ }
+ if (!isNaN(layer.options.maxZoom)) {
+ maxZoom = Math.max(maxZoom, layer.options.maxZoom);
+ }
+ }
+ }
+
+ if (i === undefined) { // we have no tilelayers
+ this._layersMaxZoom = this._layersMinZoom = undefined;
+ } else {
+ this._layersMaxZoom = maxZoom;
+ this._layersMinZoom = minZoom;
+ }
+ },
+
+ // 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 = function (id, options) {
+ return new L.Map(id, options);
+};
+
+
+/*
+ * Mercator projection that takes into account that the Earth is not a perfect sphere.
+ * Less popular than spherical mercator; used by projections like EPSG:3395.
+ */
+
+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);
+ }
+};
+
+
+
+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) {
+ this._container.parentNode.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.children,
+ edgeZIndex = -compare(Infinity, -Infinity), // -Infinity 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 () {
+
+ 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();
+ },
+
+ // Override if data stored on a tile needs to be cleaned up before reuse
+ _resetTile: function (/*tile*/) {},
+
+ _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 () {
+ 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 () {
+ 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 is used for putting WMS tile layers on the map.
+ */
+
+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
+
+ this._adjustTilePoint(tilePoint);
+
+ 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, url) + "&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 is a class that you can use as a base for creating
+ * dynamically drawn Canvas-based tile layers.
+ */
+
+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 is used to overlay images over the map (to specific geographical bounds).
+ */
+
+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),
+ origin = topLeft._add(size._multiplyBy((1 / 2) * (1 - 1 / scale)));
+
+ 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 is an image-based icon class that you can use with L.Marker for custom markers.
+ */
+
+L.Icon = L.Class.extend({
+ options: {
+ /*
+ iconUrl: (String) (required)
+ iconRetinaUrl: (String) (optional, used for retina devices if detected)
+ 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)
+ shadowRetinaUrl: (String) (optional, used for retina devices if detected)
+ 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) {
+ if (L.Browser.retina && this.options[name + 'RetinaUrl']) {
+ return this.options[name + 'RetinaUrl'];
+ }
+ return this.options[name + 'Url'];
+ }
+});
+
+L.icon = function (options) {
+ return new L.Icon(options);
+};
+
+
+/*
+ * L.Icon.Default is the blue marker icon used by default in Leaflet.
+ */
+
+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];
+ }
+
+ if (L.Browser.retina && name === 'icon') {
+ name += '@2x';
+ }
+
+ 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();
+
+ return this.fire('move', { latlng: this._latlng });
+ },
+
+ setZIndexOffset: function (offset) {
+ this.options.zIndexOffset = offset;
+ this.update();
+
+ return this;
+ },
+
+ setIcon: function (icon) {
+ if (this._map) {
+ this._removeIcon();
+ }
+
+ this.options.icon = icon;
+
+ if (this._map) {
+ this._initIcon();
+ this.update();
+ }
+
+ return this;
+ },
+
+ update: function () {
+ if (this._icon) {
+ var pos = this._map.latLngToLayerPoint(this._latlng).round();
+ this._setPos(pos);
+ }
+
+ return this;
+ },
+
+ _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; }
+
+ // TODO refactor into something shared with Map/Path/etc. to DRY it up
+
+ var icon = this._icon,
+ events = ['dblclick', 'mousedown', 'mouseover', 'mouseout', 'contextmenu'];
+
+ 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.dragging || !this.dragging._enabled) && this._map.dragging && this._map.dragging.moved()) { return; }
+
+ this.fire(e.type, {
+ originalEvent: e
+ });
+ },
+
+ _fireMouseEvent: function (e) {
+
+ this.fire(e.type, {
+ originalEvent: e
+ });
+
+ // TODO proper custom event propagation
+ // this line will always be called if marker is in a FeatureGroup
+ if (e.type === 'contextmenu' && this.hasEventListeners(e.type)) {
+ L.DomEvent.preventDefault(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 is a lightweight HTML-based icon class (as opposed to the image-based L.Icon)
+ * to use with L.Marker.
+ */
+
+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.Popup is used for displaying popups on the map.
+ */
+
+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: '',
+ zoomAnimation: true
+ },
+
+ initialize: function (options, source) {
+ L.setOptions(this, options);
+
+ this._source = source;
+ this._animated = L.Browser.any3d && this.options.zoomAnimation;
+ },
+
+ 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 (this._animated) {
+ 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-' +
+ (this._animated ? 'animated' : 'hide'),
+ 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),
+ animated = this._animated,
+ offset = this.options.offset;
+
+ if (animated) {
+ L.DomUtil.setPosition(this._container, pos);
+ }
+
+ this._containerBottom = -offset.y - (animated ? 0 : pos.y);
+ this._containerLeft = -Math.round(this._containerWidth / 2) + offset.x + (animated ? 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 (this._animated) {
+ 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 popup-related 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);
+ }
+});
+
+
+/*
+ * Adds popup-related methods to L.Map.
+ */
+
+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 into one so that
+ * 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]);
+ }
+ }
+ },
+
+ setZIndex: function (zIndex) {
+ return this.invoke('setZIndex', zIndex);
+ }
+});
+
+L.layerGroup = function (layers) {
+ return new L.LayerGroup(layers);
+};
+
+
+/*
+ * L.FeatureGroup extends L.LayerGroup by introducing mouse events and additional methods
+ * shared between a group of interactive layers (like vectors or markers).
+ */
+
+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, this._popupOptions);
+ }
+
+ 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, options) {
+ this._popupContent = content;
+ this._popupOptions = options;
+ return this.invoke('bindPopup', content, options);
+ },
+
+ 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);
+ }
+
+ this.fire('add');
+
+ 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);
+
+ // Need to fire remove event before we set _map to null as the event hooks might need the object
+ this.fire('remove');
+ this._map = null;
+
+ if (L.Browser.vml) {
+ this._container = null;
+ this._stroke = null;
+ this._fill = null;
+ }
+
+ 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);
+ }
+});
+
+
+/*
+ * Extends L.Path with SVG-specific rendering code.
+ */
+
+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 (e) {
+ var scale = this.getZoomScale(e.zoom),
+ offset = this._getCenterOffset(e.center)._multiplyBy(-scale)._add(this._pathViewport.min);
+
+ this._pathRoot.style[L.DomUtil.TRANSFORM] =
+ L.DomUtil.getTranslateString(offset) + ' 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 popup-related methods.
+ */
+
+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 instanceof Array ?
+ options.dashArray.join(' ') :
+ 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);
+
+ if (this.options.clickable) {
+ this._map.off('click', this._onClick, 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) {
+ ctx.globalAlpha = options.fillOpacity;
+ ctx.fill();
+ }
+
+ if (options.stroke) {
+ 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', {
+ latlng: e.latlng,
+ layerPoint: e.layerPoint,
+ containerPoint: e.containerPoint,
+ originalEvent: 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.)
+ */
+
+/*jshint bitwise:false */ // allow bitwise oprations for this file
+
+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;
+ },
+
+ // 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 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;
+ },
+
+ // 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.Polygon is used to display polylines on a map.
+ */
+
+L.Polyline = L.Path.extend({
+ initialize: function (latlngs, options) {
+ L.Path.prototype.initialize.call(this, options);
+
+ this._latlngs = this._convertLatLngs(latlngs);
+ },
+
+ 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 () { // (Number index, Number 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;
+ },
+
+ _convertLatLngs: function (latlngs) {
+ var i, len;
+ for (i = 0, len = latlngs.length; i < len; i++) {
+ if (L.Util.isArray(latlngs[i]) && 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 utility functions for polygons (clipping, etc.).
+ */
+
+/*jshint bitwise:false */ // allow bitwise operations 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 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;
+};
+
+
+/*
+ * 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 && L.Util.isArray(latlngs[0]) && (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;
+
+ 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 object.
+ */
+
+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()
+ ];
+ }
+});
+
+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),
+ 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);
+ },
+
+ _updateStyle : function () {
+ L.Circle.prototype._updateStyle.call(this);
+ this.setRadius(this.options.radius);
+ },
+
+ setRadius: function (radius) {
+ this.options.radius = this._radius = radius;
+ return this.redraw();
+ }
+});
+
+L.circleMarker = function (latlng, options) {
+ return new L.CircleMarker(latlng, options);
+};
+
+
+/*
+ * Extends L.Polyline to be able to manually detect clicks on Canvas-rendered polylines.
+ */
+
+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;
+ }
+});
+
+
+/*
+ * Extends L.Polygon to be able to manually detect clicks on Canvas-rendered polygons.
+ */
+
+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;
+ }
+});
+
+
+/*
+ * Extends L.Circle with Canvas-specific code.
+ */
+
+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 turns any GeoJSON data into a Leaflet layer.
+ */
+
+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 = L.Util.isArray(geojson) ? geojson : geojson.features,
+ i, len;
+
+ if (features) {
+ for (i = 0, len = features.length; i < len; i++) {
+ // Only add this if geometry or geometries are set and not null
+ if (features[i].geometries || features[i].geometry || features[i].features) {
+ 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;
+
+ layer.defaultOptions = layer.options;
+ this.resetStyle(layer);
+
+ if (options.onEachFeature) {
+ options.onEachFeature(geojson, layer);
+ }
+
+ return this.addLayer(layer);
+ },
+
+ resetStyle: function (layer) {
+ var style = this.options.style;
+ if (style) {
+ // reset any custom styles
+ L.Util.extend(layer.options, layer.defaultOptions);
+
+ 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: geometry.geometries[i],
+ type: 'Feature',
+ properties: geojson.properties
+ }, 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);
+ },
+
+ 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 = {
+ /* inspired 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);
+ }
+ if (L.Browser.touch && (type === 'dblclick') && this.addDoubleTapListener) {
+ this.addDoubleTapListener(obj, handler, id);
+ }
+
+ 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;
+
+ for (var i = L.Draggable.START.length - 1; i >= 0; i--) {
+ L.DomEvent.addListener(el, L.Draggable.START[i], stop);
+ }
+
+ return L.DomEvent
+ .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);
+ },
+
+ _getEvent: function () { // evil magic for IE
+ /*jshint noarg:false */
+ 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;
+ }
+};
+
+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'] : ['mousedown'],
+ END: {
+ mousedown: 'mouseup',
+ touchstart: 'touchend',
+ MSPointerDown: 'touchend'
+ },
+ MOVE: {
+ mousedown: 'mousemove',
+ touchstart: 'touchmove',
+ MSPointerDown: 'touchmove'
+ },
+ 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; }
+
+ for (var i = L.Draggable.START.length - 1; i >= 0; i--) {
+ L.DomEvent.on(this._dragStartTarget, L.Draggable.START[i], this._onDown, this);
+ }
+ this._enabled = true;
+ },
+
+ disable: function () {
+ if (!this._enabled) { return; }
+
+ for (var i = L.Draggable.START.length - 1; i >= 0; i--) {
+ L.DomEvent.off(this._dragStartTarget, L.Draggable.START[i], this._onDown, this);
+ }
+ 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[e.type], this._onMove, this);
+ L.DomEvent.on(document, L.Draggable.END[e.type], 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();
+ }
+
+ for (var i in L.Draggable.MOVE) {
+ if (L.Draggable.MOVE.hasOwnProperty(i)) {
+ L.DomEvent.off(document, L.Draggable.MOVE[i], this._onMove);
+ L.DomEvent.off(document, L.Draggable.END[i], 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 is a base class for handler classes that are used internally to inject
+ interaction features like dragging 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 to make the map draggable (with panning inertia), enabled by default.
+ */
+
+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: false
+});
+
+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 () {
+ // TODO fix hardcoded Earth values
+ 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 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,
+ ease = options.easeLinearity,
+
+ speedVector = direction.multiplyBy(ease / duration),
+ speed = speedVector.distanceTo(new L.Point(0, 0)),
+
+ limitedSpeed = Math.min(options.inertiaMaxSpeed, speed),
+ limitedSpeedVector = speedVector.multiplyBy(limitedSpeed / speed),
+
+ decelerationDuration = limitedSpeed / (options.inertiaDeceleration * ease),
+ offset = limitedSpeedVector.multiplyBy(-decelerationDuration / 2).round();
+
+ L.Util.requestAnimFrame(function () {
+ map.panBy(offset, decelerationDuration, ease);
+ });
+ }
+
+ 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 to handle double-click zoom on the map, enabled by default.
+ */
+
+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: true
+});
+
+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 = this._delta,
+ zoom = map.getZoom();
+
+ delta = delta > 0 ? Math.ceil(delta) : Math.round(delta);
+ 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);
+
+
+/*
+ * Extends the event handling code with double tap support for mobile browsers.
+ */
+
+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) {
+ /*jshint forin:false */
+ 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) {
+ 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;
+ }
+});
+
+
+/*
+ * Extends L.DomEvent to provide touch support for Internet Explorer and Windows-based devices.
+ */
+
+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 () {
+ 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 to add shift-drag zoom interaction to the map
+ * (zoom to a selected bounding box), enabled by default.
+ */
+
+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);
+
+ if (this._startLayerPoint.equals(layerPoint)) { return; }
+
+ var 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.Keyboard is handling keyboard interaction with the map, enabled by default.
+ */
+
+L.Map.mergeOptions({
+ keyboard: true,
+ keyboardPanOffset: 80,
+ keyboardZoomOffset: 1
+});
+
+L.Map.Keyboard = L.Handler.extend({
+
+ keyCodes: {
+ left: [37],
+ right: [39],
+ down: [40],
+ up: [38],
+ zoomIn: [187, 107, 61],
+ zoomOut: [189, 109, 173]
+ },
+
+ 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
+ .on(container, 'focus', this._onFocus, this)
+ .on(container, 'blur', this._onBlur, this)
+ .on(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
+ .off(container, 'focus', this._onFocus, this)
+ .off(container, 'blur', this._onBlur, this)
+ .off(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.on(document, 'keydown', this._onKeyDown, this);
+ },
+
+ _removeHooks: function () {
+ L.DomEvent.off(document, 'keydown', this._onKeyDown, this);
+ },
+
+ _onKeyDown: function (e) {
+ var key = e.keyCode,
+ map = this._map;
+
+ if (this._panKeys.hasOwnProperty(key)) {
+ map.panBy(this._panKeys[key]);
+
+ if (map.options.maxBounds) {
+ map.panInsideBounds(map.options.maxBounds);
+ }
+
+ } else if (this._zoomKeys.hasOwnProperty(key)) {
+ map.setZoom(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 () {
+ this._marker
+ .closePopup()
+ .fire('movestart')
+ .fire('dragstart');
+ },
+
+ _onDrag: function () {
+ 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 is an editing handler for polylines and polygons.
+ */
+
+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.Polyline.addInitHook(function () {
+
+ if (L.Handler.PolyEdit) {
+ this.editing = new L.Handler.PolyEdit(this);
+
+ if (this.options.editable) {
+ this.editing.enable();
+ }
+ }
+
+ this.on('add', function () {
+ if (this.editing && this.editing.enabled()) {
+ this.editing.addHooks();
+ }
+ });
+
+ this.on('remove', function () {
+ if (this.editing && this.editing.enabled()) {
+ this.editing.removeHooks();
+ }
+ });
+});
+
+
+/*
+ * L.Control is a base class for implementing map controls. Handles positioning.
+ * All other controls extend from this class.
+ */
+
+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);
+};
+
+
+/*
+ * Adds control-related methods to L.Map.
+ */
+
+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 is used for the default zoom buttons on the map.
+ */
+
+L.Control.Zoom = L.Control.extend({
+ options: {
+ position: 'topleft'
+ },
+
+ onAdd: function (map) {
+ var zoomName = 'leaflet-control-zoom',
+ barName = 'leaflet-bar',
+ partName = barName + '-part',
+ container = L.DomUtil.create('div', zoomName + ' ' + barName);
+
+ this._map = map;
+
+ this._zoomInButton = this._createButton('+', 'Zoom in',
+ zoomName + '-in ' +
+ partName + ' ' +
+ partName + '-top',
+ container, this._zoomIn, this);
+
+ this._zoomOutButton = this._createButton('-', 'Zoom out',
+ zoomName + '-out ' +
+ partName + ' ' +
+ partName + '-bottom',
+ container, this._zoomOut, this);
+
+ map.on('zoomend', this._updateDisabled, this);
+
+ return container;
+ },
+
+ onRemove: function (map) {
+ map.off('zoomend', this._updateDisabled, this);
+ },
+
+ _zoomIn: function (e) {
+ this._map.zoomIn(e.shiftKey ? 3 : 1);
+ },
+
+ _zoomOut: function (e) {
+ this._map.zoomOut(e.shiftKey ? 3 : 1);
+ },
+
+ _createButton: function (html, title, className, container, fn, context) {
+ var link = L.DomUtil.create('a', className, container);
+ link.innerHTML = html;
+ link.href = '#';
+ link.title = title;
+
+ var stop = L.DomEvent.stopPropagation;
+
+ L.DomEvent
+ .on(link, 'click', stop)
+ .on(link, 'mousedown', stop)
+ .on(link, 'dblclick', stop)
+ .on(link, 'click', L.DomEvent.preventDefault)
+ .on(link, 'click', fn, context);
+
+ return link;
+ },
+
+ _updateDisabled: function () {
+ var map = this._map,
+ className = 'leaflet-control-zoom-disabled';
+
+ L.DomUtil.removeClass(this._zoomInButton, className);
+ L.DomUtil.removeClass(this._zoomOutButton, className);
+
+ if (map._zoom === map.getMinZoom()) {
+ L.DomUtil.addClass(this._zoomOutButton, className);
+ }
+ if (map._zoom === map.getMaxZoom()) {
+ L.DomUtil.addClass(this._zoomInButton, className);
+ }
+ }
+});
+
+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 is used for displaying attribution on the map (added by default).
+ */
+
+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 is used for displaying metric/imperial scale on the map.
+ */
+
+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 is a control to allow users to switch between different layers on the map.
+ */
+
+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._onLayerChange, this)
+ .on('layerremove', this._onLayerChange, this);
+
+ return this._container;
+ },
+
+ onRemove: function (map) {
+ map
+ .off('layeradd', this._onLayerChange)
+ .off('layerremove', this._onLayerChange);
+ },
+
+ 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);
+ L.DomEvent.on(container, 'mousewheel', L.DomEvent.stopPropagation);
+ } 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) {
+ 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');
+ },
+
+ _onLayerChange: function (e) {
+ var id = L.stamp(e.layer);
+
+ if (this._layers[id] && !this._handlingClick) {
+ this._update();
+ }
+ },
+
+ // 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);
+
+ return 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.setZoom(this._map.getZoom());
+ 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 transitioned 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');
+ }
+
+});
+
+
+/*
+ * Extends L.Map to handle panning animations.
+ */
+
+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)._round();
+ 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);
+ }
+});
+
+
+/*
+ * Extends L.Map to handle zoom animations.
+ */
+
+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 () {
+ 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.DomUtil.removeClass(this._mapPane, 'leaflet-zoom-anim');
+ L.Util.falseFn(this._tileBg.offsetWidth); // force reflow
+ this._animatingZoom = false;
+ this._resetView(this._animateToCenter, this._animateToZoom, true, true);
+
+ 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 using browser geolocation features.
+ */
+
+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, document));
diff --git a/chimere/static/leaflet/leaflet.css b/chimere/static/leaflet/leaflet.css
new file mode 100644
index 0000000..ea3da39
--- /dev/null
+++ b/chimere/static/leaflet/leaflet.css
@@ -0,0 +1,457 @@
+/* required styles */
+
+.leaflet-map-pane,
+.leaflet-tile,
+.leaflet-marker-icon,
+.leaflet-marker-shadow,
+.leaflet-tile-pane,
+.leaflet-overlay-pane,
+.leaflet-shadow-pane,
+.leaflet-marker-pane,
+.leaflet-popup-pane,
+.leaflet-overlay-pane svg,
+.leaflet-zoom-box,
+.leaflet-image-layer,
+.leaflet-layer {
+ position: absolute;
+ left: 0;
+ top: 0;
+ }
+.leaflet-container {
+ overflow: hidden;
+ -ms-touch-action: none;
+ }
+.leaflet-tile,
+.leaflet-marker-icon,
+.leaflet-marker-shadow {
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ user-select: none;
+ }
+.leaflet-marker-icon,
+.leaflet-marker-shadow {
+ display: block;
+ }
+/* map is broken in FF if you have max-width: 100% on tiles */
+.leaflet-container img {
+ max-width: none !important;
+ }
+/* stupid Android 2 doesn't understand "max-width: none" properly */
+.leaflet-container img.leaflet-image-layer {
+ max-width: 15000px !important;
+ }
+.leaflet-tile {
+ filter: inherit;
+ visibility: hidden;
+ }
+.leaflet-tile-loaded {
+ visibility: inherit;
+ }
+.leaflet-zoom-box {
+ width: 0;
+ height: 0;
+ }
+
+.leaflet-tile-pane { z-index: 2; }
+.leaflet-objects-pane { z-index: 3; }
+.leaflet-overlay-pane { z-index: 4; }
+.leaflet-shadow-pane { z-index: 5; }
+.leaflet-marker-pane { z-index: 6; }
+.leaflet-popup-pane { z-index: 7; }
+
+
+/* control positioning */
+
+.leaflet-control {
+ position: relative;
+ z-index: 7;
+ pointer-events: auto;
+ }
+.leaflet-top,
+.leaflet-bottom {
+ position: absolute;
+ z-index: 1000;
+ pointer-events: none;
+ }
+.leaflet-top {
+ top: 0;
+ }
+.leaflet-right {
+ right: 0;
+ }
+.leaflet-bottom {
+ bottom: 0;
+ }
+.leaflet-left {
+ left: 0;
+ }
+.leaflet-control {
+ float: left;
+ clear: both;
+ }
+.leaflet-right .leaflet-control {
+ float: right;
+ }
+.leaflet-top .leaflet-control {
+ margin-top: 10px;
+ }
+.leaflet-bottom .leaflet-control {
+ margin-bottom: 10px;
+ }
+.leaflet-left .leaflet-control {
+ margin-left: 10px;
+ }
+.leaflet-right .leaflet-control {
+ margin-right: 10px;
+ }
+
+
+/* zoom and fade animations */
+
+.leaflet-fade-anim .leaflet-tile,
+.leaflet-fade-anim .leaflet-popup {
+ opacity: 0;
+ -webkit-transition: opacity 0.2s linear;
+ -moz-transition: opacity 0.2s linear;
+ -o-transition: opacity 0.2s linear;
+ transition: opacity 0.2s linear;
+ }
+.leaflet-fade-anim .leaflet-tile-loaded,
+.leaflet-fade-anim .leaflet-map-pane .leaflet-popup {
+ opacity: 1;
+ }
+
+.leaflet-zoom-anim .leaflet-zoom-animated {
+ -webkit-transition: -webkit-transform 0.25s cubic-bezier(0,0,0.25,1);
+ -moz-transition: -moz-transform 0.25s cubic-bezier(0,0,0.25,1);
+ -o-transition: -o-transform 0.25s cubic-bezier(0,0,0.25,1);
+ transition: transform 0.25s cubic-bezier(0,0,0.25,1);
+ }
+.leaflet-zoom-anim .leaflet-tile,
+.leaflet-pan-anim .leaflet-tile,
+.leaflet-touching .leaflet-zoom-animated {
+ -webkit-transition: none;
+ -moz-transition: none;
+ -o-transition: none;
+ transition: none;
+ }
+
+.leaflet-zoom-anim .leaflet-zoom-hide {
+ visibility: hidden;
+ }
+
+
+/* cursors */
+
+.leaflet-clickable {
+ cursor: pointer;
+ }
+.leaflet-container {
+ cursor: -webkit-grab;
+ cursor: -moz-grab;
+ }
+.leaflet-popup-pane,
+.leaflet-control {
+ cursor: auto;
+ }
+.leaflet-dragging,
+.leaflet-dragging .leaflet-clickable,
+.leaflet-dragging .leaflet-container {
+ cursor: move;
+ cursor: -webkit-grabbing;
+ cursor: -moz-grabbing;
+ }
+
+
+/* visual tweaks */
+
+.leaflet-container {
+ background: #ddd;
+ outline: 0;
+ }
+.leaflet-container a {
+ color: #0078A8;
+ }
+.leaflet-container a.leaflet-active {
+ outline: 2px solid orange;
+ }
+.leaflet-zoom-box {
+ border: 2px dotted #05f;
+ background: white;
+ opacity: 0.5;
+ }
+
+
+/* general typography */
+.leaflet-container {
+ font: 12px/1.5 "Helvetica Neue", Arial, Helvetica, sans-serif;
+ }
+
+
+/* general toolbar styles */
+
+.leaflet-bar {
+ box-shadow: 0 0 8px rgba(0,0,0,0.4);
+ border: 1px solid #888;
+ -webkit-border-radius: 5px;
+ border-radius: 5px;
+ }
+.leaflet-bar-part {
+ background-color: rgba(255, 255, 255, 0.8);
+ border-bottom: 1px solid #aaa;
+ }
+.leaflet-bar-part-top {
+ -webkit-border-radius: 4px 4px 0 0;
+ border-radius: 4px 4px 0 0;
+ }
+.leaflet-bar-part-bottom {
+ -webkit-border-radius: 0 0 4px 4px;
+ border-radius: 0 0 4px 4px;
+ border-bottom: none;
+ }
+
+.leaflet-touch .leaflet-bar {
+ -webkit-border-radius: 10px;
+ border-radius: 10px;
+ }
+.leaflet-touch .leaflet-bar-part {
+ border-bottom: 4px solid rgba(0,0,0,0.3);
+ }
+.leaflet-touch .leaflet-bar-part-top {
+ -webkit-border-radius: 7px 7px 0 0;
+ border-radius: 7px 7px 0 0;
+ }
+.leaflet-touch .leaflet-bar-part-bottom {
+ -webkit-border-radius: 0 0 7px 7px;
+ border-radius: 0 0 7px 7px;
+ border-bottom: none;
+ }
+
+
+/* zoom control */
+
+.leaflet-container .leaflet-control-zoom {
+ margin-left: 13px;
+ margin-top: 12px;
+ }
+.leaflet-control-zoom a {
+ width: 22px;
+ height: 22px;
+ text-align: center;
+ text-decoration: none;
+ color: black;
+ }
+.leaflet-control-zoom a,
+.leaflet-control-layers-toggle {
+ background-position: 50% 50%;
+ background-repeat: no-repeat;
+ display: block;
+ }
+.leaflet-control-zoom a:hover {
+ background-color: #fff;
+ color: #777;
+ }
+.leaflet-control-zoom-in {
+ font: bold 18px/24px Arial, Helvetica, sans-serif;
+ }
+.leaflet-control-zoom-out {
+ font: bold 23px/20px Tahoma, Verdana, sans-serif;
+ }
+.leaflet-control-zoom a.leaflet-control-zoom-disabled {
+ cursor: default;
+ background-color: rgba(255, 255, 255, 0.8);
+ color: #bbb;
+ }
+
+.leaflet-touch .leaflet-control-zoom a {
+ width: 30px;
+ height: 30px;
+ }
+.leaflet-touch .leaflet-control-zoom-in {
+ font-size: 24px;
+ line-height: 29px;
+ }
+.leaflet-touch .leaflet-control-zoom-out {
+ font-size: 28px;
+ line-height: 24px;
+ }
+
+/* layers control */
+
+.leaflet-control-layers {
+ box-shadow: 0 1px 7px rgba(0,0,0,0.4);
+ background: #f8f8f9;
+ -webkit-border-radius: 8px;
+ border-radius: 8px;
+ }
+.leaflet-control-layers-toggle {
+ background-image: url(images/layers.png);
+ width: 36px;
+ height: 36px;
+ }
+.leaflet-touch .leaflet-control-layers-toggle {
+ width: 44px;
+ height: 44px;
+ }
+.leaflet-control-layers .leaflet-control-layers-list,
+.leaflet-control-layers-expanded .leaflet-control-layers-toggle {
+ display: none;
+ }
+.leaflet-control-layers-expanded .leaflet-control-layers-list {
+ display: block;
+ position: relative;
+ }
+.leaflet-control-layers-expanded {
+ padding: 6px 10px 6px 6px;
+ color: #333;
+ background: #fff;
+ }
+.leaflet-control-layers-selector {
+ margin-top: 2px;
+ position: relative;
+ top: 1px;
+ }
+.leaflet-control-layers label {
+ display: block;
+ }
+.leaflet-control-layers-separator {
+ height: 0;
+ border-top: 1px solid #ddd;
+ margin: 5px -10px 5px -6px;
+ }
+
+
+/* attribution and scale controls */
+
+.leaflet-container .leaflet-control-attribution {
+ background-color: rgba(255, 255, 255, 0.7);
+ box-shadow: 0 0 5px #bbb;
+ margin: 0;
+ }
+.leaflet-control-attribution,
+.leaflet-control-scale-line {
+ padding: 0 5px;
+ color: #333;
+ }
+.leaflet-container .leaflet-control-attribution,
+.leaflet-container .leaflet-control-scale {
+ font-size: 11px;
+ }
+.leaflet-left .leaflet-control-scale {
+ margin-left: 5px;
+ }
+.leaflet-bottom .leaflet-control-scale {
+ margin-bottom: 5px;
+ }
+.leaflet-control-scale-line {
+ border: 2px solid #777;
+ border-top: none;
+ color: black;
+ line-height: 1.1;
+ padding: 2px 5px 1px;
+ font-size: 11px;
+ text-shadow: 1px 1px 1px #fff;
+ background-color: rgba(255, 255, 255, 0.5);
+ box-shadow: 0 -1px 5px rgba(0, 0, 0, 0.2);
+ white-space: nowrap;
+ overflow: hidden;
+ }
+.leaflet-control-scale-line:not(:first-child) {
+ border-top: 2px solid #777;
+ border-bottom: none;
+ margin-top: -2px;
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
+ }
+.leaflet-control-scale-line:not(:first-child):not(:last-child) {
+ border-bottom: 2px solid #777;
+ }
+
+.leaflet-touch .leaflet-control-attribution,
+.leaflet-touch .leaflet-control-layers,
+.leaflet-touch .leaflet-control-zoom {
+ box-shadow: none;
+ }
+.leaflet-touch .leaflet-control-layers,
+.leaflet-touch .leaflet-control-zoom {
+ border: 4px solid rgba(0,0,0,0.3);
+ }
+
+
+/* popup */
+
+.leaflet-popup {
+ position: absolute;
+ text-align: center;
+ }
+.leaflet-popup-content-wrapper {
+ padding: 1px;
+ text-align: left;
+ -webkit-border-radius: 20px;
+ border-radius: 20px;
+ }
+.leaflet-popup-content {
+ margin: 14px 20px;
+ line-height: 1.4;
+ }
+.leaflet-popup-content p {
+ margin: 18px 0;
+ }
+.leaflet-popup-tip-container {
+ margin: 0 auto;
+ width: 40px;
+ height: 20px;
+ position: relative;
+ overflow: hidden;
+ }
+.leaflet-popup-tip {
+ width: 15px;
+ height: 15px;
+ padding: 1px;
+
+ margin: -8px auto 0;
+
+ -webkit-transform: rotate(45deg);
+ -moz-transform: rotate(45deg);
+ -ms-transform: rotate(45deg);
+ -o-transform: rotate(45deg);
+ transform: rotate(45deg);
+ }
+.leaflet-popup-content-wrapper, .leaflet-popup-tip {
+ background: white;
+
+ box-shadow: 0 3px 14px rgba(0,0,0,0.4);
+ }
+.leaflet-container a.leaflet-popup-close-button {
+ position: absolute;
+ top: 0;
+ right: 0;
+ padding: 4px 5px 0 0;
+ text-align: center;
+ width: 18px;
+ height: 14px;
+ font: 16px/14px Tahoma, Verdana, sans-serif;
+ color: #c3c3c3;
+ text-decoration: none;
+ font-weight: bold;
+ background: transparent;
+ }
+.leaflet-container a.leaflet-popup-close-button:hover {
+ color: #999;
+ }
+.leaflet-popup-scrolled {
+ overflow: auto;
+ border-bottom: 1px solid #ddd;
+ border-top: 1px solid #ddd;
+ }
+
+
+/* div icon */
+
+.leaflet-div-icon {
+ background: #fff;
+ border: 1px solid #666;
+ }
+.leaflet-editing-icon {
+ -webkit-border-radius: 2px;
+ border-radius: 2px;
+ }
diff --git a/chimere/static/leaflet/leaflet.ie.css b/chimere/static/leaflet/leaflet.ie.css
new file mode 100644
index 0000000..b59c814
--- /dev/null
+++ b/chimere/static/leaflet/leaflet.ie.css
@@ -0,0 +1,57 @@
+.leaflet-vml-shape {
+ width: 1px;
+ height: 1px;
+ }
+.lvml {
+ behavior: url(#default#VML);
+ display: inline-block;
+ position: absolute;
+ }
+
+.leaflet-control {
+ display: inline;
+ }
+
+.leaflet-popup-tip {
+ width: 21px;
+ _width: 27px;
+ margin: 0 auto;
+ _margin-top: -3px;
+
+ filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678);
+ -ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)";
+ }
+.leaflet-popup-tip-container {
+ margin-top: -1px;
+ }
+.leaflet-popup-content-wrapper, .leaflet-popup-tip {
+ border: 1px solid #999;
+ }
+.leaflet-popup-content-wrapper {
+ zoom: 1;
+ }
+
+.leaflet-control-zoom,
+.leaflet-control-layers {
+ border: 3px solid #999;
+ }
+.leaflet-control-zoom a {
+ background-color: #eee;
+ }
+.leaflet-control-zoom a:hover {
+ background-color: #fff;
+ }
+.leaflet-control-layers-toggle {
+ }
+.leaflet-control-attribution,
+.leaflet-control-layers,
+.leaflet-control-scale-line {
+ background: white;
+ }
+.leaflet-zoom-box {
+ filter: alpha(opacity=50);
+ }
+.leaflet-control-attribution {
+ border-top: 1px solid #bbb;
+ border-left: 1px solid #bbb;
+ }
diff --git a/chimere/static/leaflet/leaflet.js b/chimere/static/leaflet/leaflet.js
new file mode 100644
index 0000000..bdf0b69
--- /dev/null
+++ b/chimere/static/leaflet/leaflet.js
@@ -0,0 +1,8 @@
+/*
+ Leaflet, a JavaScript library for mobile-friendly interactive maps. http://leafletjs.com
+ (c) 2010-2013, Vladimir Agafonkin, CloudMade
+*/
+(function(t,e,i){var n,o;typeof exports!=i+""?n=exports:(o=t.L,n={},n.noConflict=function(){return t.L=o,this},t.L=n),n.version="0.5.1",n.Util={extend:function(t){var e,i,n,o,s=Array.prototype.slice.call(arguments,1);for(i=0,n=s.length;n>i;i++){o=s[i]||{};for(e in o)o.hasOwnProperty(e)&&(t[e]=o[e])}return t},bind:function(t,e){var i=arguments.length>2?Array.prototype.slice.call(arguments,2):null;return function(){return t.apply(e,i||arguments)}},stamp:function(){var t=0,e="_leaflet_id";return function(i){return i[e]=i[e]||++t,i[e]}}(),limitExecByInterval:function(t,e,n){var o,s;return function a(){var r=arguments;return o?(s=!0,i):(o=!0,setTimeout(function(){o=!1,s&&(a.apply(n,r),s=!1)},e),t.apply(n,r),i)}},falseFn:function(){return!1},formatNum:function(t,e){var i=Math.pow(10,e||5);return Math.round(t*i)/i},splitWords:function(t){return t.replace(/^\s+|\s+$/g,"").split(/\s+/)},setOptions:function(t,e){return t.options=n.extend({},t.options,e),t.options},getParamString:function(t,e){var i=[];for(var n in t)t.hasOwnProperty(n)&&i.push(n+"="+t[n]);return(e&&-1!==e.indexOf("?")?"&":"?")+i.join("&")},template:function(t,e){return t.replace(/\{ *([\w_]+) *\}/g,function(t,i){var n=e[i];if(!e.hasOwnProperty(i))throw Error("No value provided for variable "+t);return n})},isArray:function(t){return"[object Array]"===Object.prototype.toString.call(t)},emptyImageUrl:"data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs="},function(){function e(e){var i,n,o=["webkit","moz","o","ms"];for(i=0;o.length>i&&!n;i++)n=t[o[i]+e];return n}function o(e){var i=+new Date,n=Math.max(0,16-(i-s));return s=i+n,t.setTimeout(e,n)}var s=0,a=t.requestAnimationFrame||e("RequestAnimationFrame")||o,r=t.cancelAnimationFrame||e("CancelAnimationFrame")||e("CancelRequestAnimationFrame")||function(e){t.clearTimeout(e)};n.Util.requestAnimFrame=function(e,s,r,h){return e=n.bind(e,s),r&&a===o?(e(),i):a.call(t,e,h)},n.Util.cancelAnimFrame=function(e){e&&r.call(t,e)}}(),n.extend=n.Util.extend,n.bind=n.Util.bind,n.stamp=n.Util.stamp,n.setOptions=n.Util.setOptions,n.Class=function(){},n.Class.extend=function(t){var e=function(){this.initialize&&this.initialize.apply(this,arguments),this._initHooks&&this.callInitHooks()},i=function(){};i.prototype=this.prototype;var o=new i;o.constructor=e,e.prototype=o;for(var s in this)this.hasOwnProperty(s)&&"prototype"!==s&&(e[s]=this[s]);t.statics&&(n.extend(e,t.statics),delete t.statics),t.includes&&(n.Util.extend.apply(null,[o].concat(t.includes)),delete t.includes),t.options&&o.options&&(t.options=n.extend({},o.options,t.options)),n.extend(o,t),o._initHooks=[];var a=this;return o.callInitHooks=function(){if(!this._initHooksCalled){a.prototype.callInitHooks&&a.prototype.callInitHooks.call(this),this._initHooksCalled=!0;for(var t=0,e=o._initHooks.length;e>t;t++)o._initHooks[t].call(this)}},e},n.Class.include=function(t){n.extend(this.prototype,t)},n.Class.mergeOptions=function(t){n.extend(this.prototype.options,t)},n.Class.addInitHook=function(t){var e=Array.prototype.slice.call(arguments,1),i="function"==typeof t?t:function(){this[t].apply(this,e)};this.prototype._initHooks=this.prototype._initHooks||[],this.prototype._initHooks.push(i)};var s="_leaflet_events";n.Mixin={},n.Mixin.Events={addEventListener:function(t,e,i){var o,a,r,h=this[s]=this[s]||{};if("object"==typeof t){for(o in t)t.hasOwnProperty(o)&&this.addEventListener(o,t[o],e);return this}for(t=n.Util.splitWords(t),a=0,r=t.length;r>a;a++)h[t[a]]=h[t[a]]||[],h[t[a]].push({action:e,context:i||this});return this},hasEventListeners:function(t){return s in this&&t in this[s]&&this[s][t].length>0},removeEventListener:function(t,e,i){var o,a,r,h,l,u=this[s];if("object"==typeof t){for(o in t)t.hasOwnProperty(o)&&this.removeEventListener(o,t[o],e);return this}for(t=n.Util.splitWords(t),a=0,r=t.length;r>a;a++)if(this.hasEventListeners(t[a]))for(h=u[t[a]],l=h.length-1;l>=0;l--)e&&h[l].action!==e||i&&h[l].context!==i||h.splice(l,1);return this},fireEvent:function(t,e){if(!this.hasEventListeners(t))return this;for(var i=n.extend({type:t,target:this},e),o=this[s][t].slice(),a=0,r=o.length;r>a;a++)o[a].action.call(o[a].context||this,i);return this}},n.Mixin.Events.on=n.Mixin.Events.addEventListener,n.Mixin.Events.off=n.Mixin.Events.removeEventListener,n.Mixin.Events.fire=n.Mixin.Events.fireEvent,function(){var o=!!t.ActiveXObject,s=o&&!t.XMLHttpRequest,a=o&&!e.querySelector,r=navigator.userAgent.toLowerCase(),h=-1!==r.indexOf("webkit"),l=-1!==r.indexOf("chrome"),u=-1!==r.indexOf("android"),c=-1!==r.search("android [23]"),_=typeof orientation!=i+"",d=t.navigator&&t.navigator.msPointerEnabled&&t.navigator.msMaxTouchPoints,p="devicePixelRatio"in t&&t.devicePixelRatio>1||"matchMedia"in t&&t.matchMedia("(min-resolution:144dpi)")&&t.matchMedia("(min-resolution:144dpi)").matches,m=e.documentElement,f=o&&"transition"in m.style,g="WebKitCSSMatrix"in t&&"m11"in new t.WebKitCSSMatrix,v="MozPerspective"in m.style,y="OTransition"in m.style,L=!t.L_DISABLE_3D&&(f||g||v||y),P=!t.L_NO_TOUCH&&function(){var t="ontouchstart";if(d||t in m)return!0;var i=e.createElement("div"),n=!1;return i.setAttribute?(i.setAttribute(t,"return;"),"function"==typeof i[t]&&(n=!0),i.removeAttribute(t),i=null,n):!1}();n.Browser={ie:o,ie6:s,ie7:a,webkit:h,android:u,android23:c,chrome:l,ie3d:f,webkit3d:g,gecko3d:v,opera3d:y,any3d:L,mobile:_,mobileWebkit:_&&h,mobileWebkit3d:_&&g,mobileOpera:_&&t.opera,touch:P,msTouch:d,retina:p}}(),n.Point=function(t,e,i){this.x=i?Math.round(t):t,this.y=i?Math.round(e):e},n.Point.prototype={clone:function(){return new n.Point(this.x,this.y)},add:function(t){return this.clone()._add(n.point(t))},_add:function(t){return this.x+=t.x,this.y+=t.y,this},subtract:function(t){return this.clone()._subtract(n.point(t))},_subtract:function(t){return this.x-=t.x,this.y-=t.y,this},divideBy:function(t){return this.clone()._divideBy(t)},_divideBy:function(t){return this.x/=t,this.y/=t,this},multiplyBy:function(t){return this.clone()._multiplyBy(t)},_multiplyBy:function(t){return this.x*=t,this.y*=t,this},round:function(){return this.clone()._round()},_round:function(){return this.x=Math.round(this.x),this.y=Math.round(this.y),this},floor:function(){return this.clone()._floor()},_floor:function(){return this.x=Math.floor(this.x),this.y=Math.floor(this.y),this},distanceTo:function(t){t=n.point(t);var e=t.x-this.x,i=t.y-this.y;return Math.sqrt(e*e+i*i)},equals:function(t){return t.x===this.x&&t.y===this.y},toString:function(){return"Point("+n.Util.formatNum(this.x)+", "+n.Util.formatNum(this.y)+")"}},n.point=function(t,e,i){return t instanceof n.Point?t:n.Util.isArray(t)?new n.Point(t[0],t[1]):isNaN(t)?t:new n.Point(t,e,i)},n.Bounds=function(t,e){if(t)for(var i=e?[t,e]:t,n=0,o=i.length;o>n;n++)this.extend(i[n])},n.Bounds.prototype={extend:function(t){return t=n.point(t),this.min||this.max?(this.min.x=Math.min(t.x,this.min.x),this.max.x=Math.max(t.x,this.max.x),this.min.y=Math.min(t.y,this.min.y),this.max.y=Math.max(t.y,this.max.y)):(this.min=t.clone(),this.max=t.clone()),this},getCenter:function(t){return new n.Point((this.min.x+this.max.x)/2,(this.min.y+this.max.y)/2,t)},getBottomLeft:function(){return new n.Point(this.min.x,this.max.y)},getTopRight:function(){return new n.Point(this.max.x,this.min.y)},getSize:function(){return this.max.subtract(this.min)},contains:function(t){var e,i;return t="number"==typeof t[0]||t instanceof n.Point?n.point(t):n.bounds(t),t instanceof n.Bounds?(e=t.min,i=t.max):e=i=t,e.x>=this.min.x&&i.x<=this.max.x&&e.y>=this.min.y&&i.y<=this.max.y},intersects:function(t){t=n.bounds(t);var e=this.min,i=this.max,o=t.min,s=t.max,a=s.x>=e.x&&o.x<=i.x,r=s.y>=e.y&&o.y<=i.y;return a&&r},isValid:function(){return!(!this.min||!this.max)}},n.bounds=function(t,e){return!t||t instanceof n.Bounds?t:new n.Bounds(t,e)},n.Transformation=function(t,e,i,n){this._a=t,this._b=e,this._c=i,this._d=n},n.Transformation.prototype={transform:function(t,e){return this._transform(t.clone(),e)},_transform:function(t,e){return e=e||1,t.x=e*(this._a*t.x+this._b),t.y=e*(this._c*t.y+this._d),t},untransform:function(t,e){return e=e||1,new n.Point((t.x/e-this._b)/this._a,(t.y/e-this._d)/this._c)}},n.DomUtil={get:function(t){return"string"==typeof t?e.getElementById(t):t},getStyle:function(t,i){var n=t.style[i];if(!n&&t.currentStyle&&(n=t.currentStyle[i]),(!n||"auto"===n)&&e.defaultView){var o=e.defaultView.getComputedStyle(t,null);n=o?o[i]:null}return"auto"===n?null:n},getViewportOffset:function(t){var i,o=0,s=0,a=t,r=e.body,h=n.Browser.ie7;do{if(o+=a.offsetTop||0,s+=a.offsetLeft||0,o+=parseInt(n.DomUtil.getStyle(a,"borderTopWidth"),10)||0,s+=parseInt(n.DomUtil.getStyle(a,"borderLeftWidth"),10)||0,i=n.DomUtil.getStyle(a,"position"),a.offsetParent===r&&"absolute"===i)break;if("fixed"===i){o+=r.scrollTop||0,s+=r.scrollLeft||0;break}a=a.offsetParent}while(a);a=t;do{if(a===r)break;o-=a.scrollTop||0,s-=a.scrollLeft||0,n.DomUtil.documentIsLtr()||!n.Browser.webkit&&!h||(s+=a.scrollWidth-a.clientWidth,h&&"hidden"!==n.DomUtil.getStyle(a,"overflow-y")&&"hidden"!==n.DomUtil.getStyle(a,"overflow")&&(s+=17)),a=a.parentNode}while(a);return new n.Point(s,o)},documentIsLtr:function(){return n.DomUtil._docIsLtrCached||(n.DomUtil._docIsLtrCached=!0,n.DomUtil._docIsLtr="ltr"===n.DomUtil.getStyle(e.body,"direction")),n.DomUtil._docIsLtr},create:function(t,i,n){var o=e.createElement(t);return o.className=i,n&&n.appendChild(o),o},disableTextSelection:function(){e.selection&&e.selection.empty&&e.selection.empty(),this._onselectstart||(this._onselectstart=e.onselectstart||null,e.onselectstart=n.Util.falseFn)},enableTextSelection:function(){e.onselectstart===n.Util.falseFn&&(e.onselectstart=this._onselectstart,this._onselectstart=null)},hasClass:function(t,e){return t.className.length>0&&RegExp("(^|\\s)"+e+"(\\s|$)").test(t.className)},addClass:function(t,e){n.DomUtil.hasClass(t,e)||(t.className+=(t.className?" ":"")+e)},removeClass:function(t,e){function i(t,i){return i===e?"":t}t.className=t.className.replace(/(\S+)\s*/g,i).replace(/(^\s+|\s+$)/,"")},setOpacity:function(t,e){if("opacity"in t.style)t.style.opacity=e;else if("filter"in t.style){var i=!1,n="DXImageTransform.Microsoft.Alpha";try{i=t.filters.item(n)}catch(o){}e=Math.round(100*e),i?(i.Enabled=100!==e,i.Opacity=e):t.style.filter+=" progid:"+n+"(opacity="+e+")"}},testProp:function(t){for(var i=e.documentElement.style,n=0;t.length>n;n++)if(t[n]in i)return t[n];return!1},getTranslateString:function(t){var e=n.Browser.webkit3d,i="translate"+(e?"3d":"")+"(",o=(e?",0":"")+")";return i+t.x+"px,"+t.y+"px"+o},getScaleString:function(t,e){var i=n.DomUtil.getTranslateString(e.add(e.multiplyBy(-1*t))),o=" scale("+t+") ";return i+o},setPosition:function(t,e,i){t._leaflet_pos=e,!i&&n.Browser.any3d?(t.style[n.DomUtil.TRANSFORM]=n.DomUtil.getTranslateString(e),n.Browser.mobileWebkit3d&&(t.style.WebkitBackfaceVisibility="hidden")):(t.style.left=e.x+"px",t.style.top=e.y+"px")},getPosition:function(t){return t._leaflet_pos}},n.DomUtil.TRANSFORM=n.DomUtil.testProp(["transform","WebkitTransform","OTransform","MozTransform","msTransform"]),n.DomUtil.TRANSITION=n.DomUtil.testProp(["webkitTransition","transition","OTransition","MozTransition","msTransition"]),n.DomUtil.TRANSITION_END="webkitTransition"===n.DomUtil.TRANSITION||"OTransition"===n.DomUtil.TRANSITION?n.DomUtil.TRANSITION+"End":"transitionend",n.LatLng=function(t,e){var i=parseFloat(t),n=parseFloat(e);if(isNaN(i)||isNaN(n))throw Error("Invalid LatLng object: ("+t+", "+e+")");this.lat=i,this.lng=n},n.extend(n.LatLng,{DEG_TO_RAD:Math.PI/180,RAD_TO_DEG:180/Math.PI,MAX_MARGIN:1e-9}),n.LatLng.prototype={equals:function(t){if(!t)return!1;t=n.latLng(t);var e=Math.max(Math.abs(this.lat-t.lat),Math.abs(this.lng-t.lng));return n.LatLng.MAX_MARGIN>=e},toString:function(t){return"LatLng("+n.Util.formatNum(this.lat,t)+", "+n.Util.formatNum(this.lng,t)+")"},distanceTo:function(t){t=n.latLng(t);var e=6378137,i=n.LatLng.DEG_TO_RAD,o=(t.lat-this.lat)*i,s=(t.lng-this.lng)*i,a=this.lat*i,r=t.lat*i,h=Math.sin(o/2),l=Math.sin(s/2),u=h*h+l*l*Math.cos(a)*Math.cos(r);return 2*e*Math.atan2(Math.sqrt(u),Math.sqrt(1-u))},wrap:function(t,e){var i=this.lng;return t=t||-180,e=e||180,i=(i+e)%(e-t)+(t>i||i===e?e:t),new n.LatLng(this.lat,i)}},n.latLng=function(t,e){return t instanceof n.LatLng?t:n.Util.isArray(t)?new n.LatLng(t[0],t[1]):isNaN(t)?t:new n.LatLng(t,e)},n.LatLngBounds=function(t,e){if(t)for(var i=e?[t,e]:t,n=0,o=i.length;o>n;n++)this.extend(i[n])},n.LatLngBounds.prototype={extend:function(t){return t="number"==typeof t[0]||"string"==typeof t[0]||t instanceof n.LatLng?n.latLng(t):n.latLngBounds(t),t instanceof n.LatLng?this._southWest||this._northEast?(this._southWest.lat=Math.min(t.lat,this._southWest.lat),this._southWest.lng=Math.min(t.lng,this._southWest.lng),this._northEast.lat=Math.max(t.lat,this._northEast.lat),this._northEast.lng=Math.max(t.lng,this._northEast.lng)):(this._southWest=new n.LatLng(t.lat,t.lng),this._northEast=new n.LatLng(t.lat,t.lng)):t instanceof n.LatLngBounds&&(this.extend(t._southWest),this.extend(t._northEast)),this},pad:function(t){var e=this._southWest,i=this._northEast,o=Math.abs(e.lat-i.lat)*t,s=Math.abs(e.lng-i.lng)*t;return new n.LatLngBounds(new n.LatLng(e.lat-o,e.lng-s),new n.LatLng(i.lat+o,i.lng+s))},getCenter:function(){return new n.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 n.LatLng(this._northEast.lat,this._southWest.lng)},getSouthEast:function(){return new n.LatLng(this._southWest.lat,this._northEast.lng)},contains:function(t){t="number"==typeof t[0]||t instanceof n.LatLng?n.latLng(t):n.latLngBounds(t);var e,i,o=this._southWest,s=this._northEast;return t instanceof n.LatLngBounds?(e=t.getSouthWest(),i=t.getNorthEast()):e=i=t,e.lat>=o.lat&&i.lat<=s.lat&&e.lng>=o.lng&&i.lng<=s.lng},intersects:function(t){t=n.latLngBounds(t);var e=this._southWest,i=this._northEast,o=t.getSouthWest(),s=t.getNorthEast(),a=s.lat>=e.lat&&o.lat<=i.lat,r=s.lng>=e.lng&&o.lng<=i.lng;return a&&r},toBBoxString:function(){var t=this._southWest,e=this._northEast;return[t.lng,t.lat,e.lng,e.lat].join(",")},equals:function(t){return t?(t=n.latLngBounds(t),this._southWest.equals(t.getSouthWest())&&this._northEast.equals(t.getNorthEast())):!1},isValid:function(){return!(!this._southWest||!this._northEast)}},n.latLngBounds=function(t,e){return!t||t instanceof n.LatLngBounds?t:new n.LatLngBounds(t,e)},n.Projection={},n.Projection.SphericalMercator={MAX_LATITUDE:85.0511287798,project:function(t){var e=n.LatLng.DEG_TO_RAD,i=this.MAX_LATITUDE,o=Math.max(Math.min(i,t.lat),-i),s=t.lng*e,a=o*e;return a=Math.log(Math.tan(Math.PI/4+a/2)),new n.Point(s,a)},unproject:function(t){var e=n.LatLng.RAD_TO_DEG,i=t.x*e,o=(2*Math.atan(Math.exp(t.y))-Math.PI/2)*e;return new n.LatLng(o,i)}},n.Projection.LonLat={project:function(t){return new n.Point(t.lng,t.lat)},unproject:function(t){return new n.LatLng(t.y,t.x)}},n.CRS={latLngToPoint:function(t,e){var i=this.projection.project(t),n=this.scale(e);return this.transformation._transform(i,n)},pointToLatLng:function(t,e){var i=this.scale(e),n=this.transformation.untransform(t,i);return this.projection.unproject(n)},project:function(t){return this.projection.project(t)},scale:function(t){return 256*Math.pow(2,t)}},n.CRS.Simple=n.extend({},n.CRS,{projection:n.Projection.LonLat,transformation:new n.Transformation(1,0,-1,0),scale:function(t){return Math.pow(2,t)}}),n.CRS.EPSG3857=n.extend({},n.CRS,{code:"EPSG:3857",projection:n.Projection.SphericalMercator,transformation:new n.Transformation(.5/Math.PI,.5,-.5/Math.PI,.5),project:function(t){var e=this.projection.project(t),i=6378137;return e.multiplyBy(i)}}),n.CRS.EPSG900913=n.extend({},n.CRS.EPSG3857,{code:"EPSG:900913"}),n.CRS.EPSG4326=n.extend({},n.CRS,{code:"EPSG:4326",projection:n.Projection.LonLat,transformation:new n.Transformation(1/360,.5,-1/360,.5)}),n.Map=n.Class.extend({includes:n.Mixin.Events,options:{crs:n.CRS.EPSG3857,fadeAnimation:n.DomUtil.TRANSITION&&!n.Browser.android23,trackResize:!0,markerZoomAnimation:n.DomUtil.TRANSITION&&n.Browser.any3d},initialize:function(t,e){e=n.setOptions(this,e),this._initContainer(t),this._initLayout(),this.callInitHooks(),this._initEvents(),e.maxBounds&&this.setMaxBounds(e.maxBounds),e.center&&e.zoom!==i&&this.setView(n.latLng(e.center),e.zoom,!0),this._initLayers(e.layers)},setView:function(t,e){return this._resetView(n.latLng(t),this._limitZoom(e)),this},setZoom:function(t){return this.setView(this.getCenter(),t)},zoomIn:function(t){return this.setZoom(this._zoom+(t||1))},zoomOut:function(t){return this.setZoom(this._zoom-(t||1))},fitBounds:function(t){var e=this.getBoundsZoom(t);return this.setView(n.latLngBounds(t).getCenter(),e)},fitWorld:function(){var t=new n.LatLng(-60,-170),e=new n.LatLng(85,179);return this.fitBounds(new n.LatLngBounds(t,e))},panTo:function(t){return this.setView(t,this._zoom)},panBy:function(t){return this.fire("movestart"),this._rawPanBy(n.point(t)),this.fire("move"),this.fire("moveend")},setMaxBounds:function(t){if(t=n.latLngBounds(t),this.options.maxBounds=t,!t)return this._boundsMinZoom=null,this;var e=this.getBoundsZoom(t,!0);return this._boundsMinZoom=e,this._loaded&&(e>this._zoom?this.setView(t.getCenter(),e):this.panInsideBounds(t)),this},panInsideBounds:function(t){t=n.latLngBounds(t);var e=this.getBounds(),i=this.project(e.getSouthWest()),o=this.project(e.getNorthEast()),s=this.project(t.getSouthWest()),a=this.project(t.getNorthEast()),r=0,h=0;return o.y<a.y&&(h=a.y-o.y),o.x>a.x&&(r=a.x-o.x),i.y>s.y&&(h=s.y-i.y),i.x<s.x&&(r=s.x-i.x),this.panBy(new n.Point(r,h,!0))},addLayer:function(t){var e=n.stamp(t);return this._layers[e]?this:(this._layers[e]=t,!t.options||isNaN(t.options.maxZoom)&&isNaN(t.options.minZoom)||(this._zoomBoundLayers[e]=t,this._updateZoomLevels()),this.options.zoomAnimation&&n.TileLayer&&t instanceof n.TileLayer&&(this._tileLayersNum++,this._tileLayersToLoad++,t.on("load",this._onTileLayerLoad,this)),this.whenReady(function(){t.onAdd(this),this.fire("layeradd",{layer:t})},this),this)},removeLayer:function(t){var e=n.stamp(t);if(this._layers[e])return t.onRemove(this),delete this._layers[e],this._zoomBoundLayers[e]&&(delete this._zoomBoundLayers[e],this._updateZoomLevels()),this.options.zoomAnimation&&n.TileLayer&&t instanceof n.TileLayer&&(this._tileLayersNum--,this._tileLayersToLoad--,t.off("load",this._onTileLayerLoad,this)),this.fire("layerremove",{layer:t})},hasLayer:function(t){var e=n.stamp(t);return this._layers.hasOwnProperty(e)},invalidateSize:function(t){var e=this.getSize();if(this._sizeChanged=!0,this.options.maxBounds&&this.setMaxBounds(this.options.maxBounds),!this._loaded)return this;var i=e._subtract(this.getSize())._divideBy(2)._round();return t===!0?this.panBy(i):(this._rawPanBy(i),this.fire("move"),clearTimeout(this._sizeTimer),this._sizeTimer=setTimeout(n.bind(this.fire,this,"moveend"),200)),this},addHandler:function(t,e){return e?(this[t]=new e(this),this.options[t]&&this[t].enable(),this):i},getCenter:function(){return this.layerPointToLatLng(this._getCenterLayerPoint())},getZoom:function(){return this._zoom},getBounds:function(){var t=this.getPixelBounds(),e=this.unproject(t.getBottomLeft()),i=this.unproject(t.getTopRight());return new n.LatLngBounds(e,i)},getMinZoom:function(){var t=this.options.minZoom||0,e=this._layersMinZoom||0,i=this._boundsMinZoom||0;return Math.max(t,e,i)},getMaxZoom:function(){var t=this.options.maxZoom===i?1/0:this.options.maxZoom,e=this._layersMaxZoom===i?1/0:this._layersMaxZoom;return Math.min(t,e)},getBoundsZoom:function(t,e){t=n.latLngBounds(t);var i,o,s,a=this.getSize(),r=this.options.minZoom||0,h=this.getMaxZoom(),l=t.getNorthEast(),u=t.getSouthWest(),c=!0;e&&r--;do r++,o=this.project(l,r),s=this.project(u,r),i=new n.Point(Math.abs(o.x-s.x),Math.abs(s.y-o.y)),c=e?i.x<a.x||i.y<a.y:i.x<=a.x&&i.y<=a.y;while(c&&h>=r);return c&&e?null:e?r:r-1},getSize:function(){return(!this._size||this._sizeChanged)&&(this._size=new n.Point(this._container.clientWidth,this._container.clientHeight),this._sizeChanged=!1),this._size.clone()},getPixelBounds:function(){var t=this._getTopLeftPoint();return new n.Bounds(t,t.add(this.getSize()))},getPixelOrigin:function(){return this._initialTopLeftPoint},getPanes:function(){return this._panes},getContainer:function(){return this._container},getZoomScale:function(t){var e=this.options.crs;return e.scale(t)/e.scale(this._zoom)},getScaleZoom:function(t){return this._zoom+Math.log(t)/Math.LN2},project:function(t,e){return e=e===i?this._zoom:e,this.options.crs.latLngToPoint(n.latLng(t),e)},unproject:function(t,e){return e=e===i?this._zoom:e,this.options.crs.pointToLatLng(n.point(t),e)},layerPointToLatLng:function(t){var e=n.point(t).add(this._initialTopLeftPoint);return this.unproject(e)},latLngToLayerPoint:function(t){var e=this.project(n.latLng(t))._round();return e._subtract(this._initialTopLeftPoint)},containerPointToLayerPoint:function(t){return n.point(t).subtract(this._getMapPanePos())},layerPointToContainerPoint:function(t){return n.point(t).add(this._getMapPanePos())},containerPointToLatLng:function(t){var e=this.containerPointToLayerPoint(n.point(t));return this.layerPointToLatLng(e)},latLngToContainerPoint:function(t){return this.layerPointToContainerPoint(this.latLngToLayerPoint(n.latLng(t)))},mouseEventToContainerPoint:function(t){return n.DomEvent.getMousePosition(t,this._container)},mouseEventToLayerPoint:function(t){return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(t))},mouseEventToLatLng:function(t){return this.layerPointToLatLng(this.mouseEventToLayerPoint(t))},_initContainer:function(t){var e=this._container=n.DomUtil.get(t);if(e._leaflet)throw Error("Map container is already initialized.");e._leaflet=!0},_initLayout:function(){var t=this._container;n.DomUtil.addClass(t,"leaflet-container"),n.Browser.touch&&n.DomUtil.addClass(t,"leaflet-touch"),this.options.fadeAnimation&&n.DomUtil.addClass(t,"leaflet-fade-anim");var e=n.DomUtil.getStyle(t,"position");"absolute"!==e&&"relative"!==e&&"fixed"!==e&&(t.style.position="relative"),this._initPanes(),this._initControlPos&&this._initControlPos()},_initPanes:function(){var t=this._panes={};this._mapPane=t.mapPane=this._createPane("leaflet-map-pane",this._container),this._tilePane=t.tilePane=this._createPane("leaflet-tile-pane",this._mapPane),t.objectsPane=this._createPane("leaflet-objects-pane",this._mapPane),t.shadowPane=this._createPane("leaflet-shadow-pane"),t.overlayPane=this._createPane("leaflet-overlay-pane"),t.markerPane=this._createPane("leaflet-marker-pane"),t.popupPane=this._createPane("leaflet-popup-pane");var e=" leaflet-zoom-hide";this.options.markerZoomAnimation||(n.DomUtil.addClass(t.markerPane,e),n.DomUtil.addClass(t.shadowPane,e),n.DomUtil.addClass(t.popupPane,e))},_createPane:function(t,e){return n.DomUtil.create("div",t,e||this._panes.objectsPane)},_initLayers:function(t){t=t?n.Util.isArray(t)?t:[t]:[],this._layers={},this._zoomBoundLayers={},this._tileLayersNum=0;var e,i;for(e=0,i=t.length;i>e;e++)this.addLayer(t[e])},_resetView:function(t,e,i,o){var s=this._zoom!==e;o||(this.fire("movestart"),s&&this.fire("zoomstart")),this._zoom=e,this._initialTopLeftPoint=this._getNewTopLeftPoint(t),i?this._initialTopLeftPoint._add(this._getMapPanePos()):n.DomUtil.setPosition(this._mapPane,new n.Point(0,0)),this._tileLayersToLoad=this._tileLayersNum;var a=!this._loaded;this._loaded=!0,this.fire("viewreset",{hard:!i}),this.fire("move"),(s||o)&&this.fire("zoomend"),this.fire("moveend",{hard:!i}),a&&this.fire("load")},_rawPanBy:function(t){n.DomUtil.setPosition(this._mapPane,this._getMapPanePos().subtract(t))},_updateZoomLevels:function(){var t,e=1/0,n=-1/0;for(t in this._zoomBoundLayers)if(this._zoomBoundLayers.hasOwnProperty(t)){var o=this._zoomBoundLayers[t];isNaN(o.options.minZoom)||(e=Math.min(e,o.options.minZoom)),isNaN(o.options.maxZoom)||(n=Math.max(n,o.options.maxZoom))}t===i?this._layersMaxZoom=this._layersMinZoom=i:(this._layersMaxZoom=n,this._layersMinZoom=e)},_initEvents:function(){if(n.DomEvent){n.DomEvent.on(this._container,"click",this._onMouseClick,this);var e,i,o=["dblclick","mousedown","mouseup","mouseenter","mouseleave","mousemove","contextmenu"];for(e=0,i=o.length;i>e;e++)n.DomEvent.on(this._container,o[e],this._fireMouseEvent,this);this.options.trackResize&&n.DomEvent.on(t,"resize",this._onResize,this)}},_onResize:function(){n.Util.cancelAnimFrame(this._resizeRequest),this._resizeRequest=n.Util.requestAnimFrame(this.invalidateSize,this,!1,this._container)},_onMouseClick:function(t){!this._loaded||this.dragging&&this.dragging.moved()||(this.fire("preclick"),this._fireMouseEvent(t))},_fireMouseEvent:function(t){if(this._loaded){var e=t.type;if(e="mouseenter"===e?"mouseover":"mouseleave"===e?"mouseout":e,this.hasEventListeners(e)){"contextmenu"===e&&n.DomEvent.preventDefault(t);var i=this.mouseEventToContainerPoint(t),o=this.containerPointToLayerPoint(i),s=this.layerPointToLatLng(o);this.fire(e,{latlng:s,layerPoint:o,containerPoint:i,originalEvent:t})}}},_onTileLayerLoad:function(){this._tileLayersToLoad--,this._tileLayersNum&&!this._tileLayersToLoad&&this._tileBg&&(clearTimeout(this._clearTileBgTimer),this._clearTileBgTimer=setTimeout(n.bind(this._clearTileBg,this),500))},whenReady:function(t,e){return this._loaded?t.call(e||this,this):this.on("load",t,e),this},_getMapPanePos:function(){return n.DomUtil.getPosition(this._mapPane)},_getTopLeftPoint:function(){if(!this._loaded)throw Error("Set map center and zoom first.");return this._initialTopLeftPoint.subtract(this._getMapPanePos())},_getNewTopLeftPoint:function(t,e){var i=this.getSize()._divideBy(2);return this.project(t,e)._subtract(i)._round()},_latLngToNewLayerPoint:function(t,e,i){var n=this._getNewTopLeftPoint(i,e).add(this._getMapPanePos());return this.project(t,e)._subtract(n)},_getCenterLayerPoint:function(){return this.containerPointToLayerPoint(this.getSize()._divideBy(2))},_getCenterOffset:function(t){return this.latLngToLayerPoint(t).subtract(this._getCenterLayerPoint())},_limitZoom:function(t){var e=this.getMinZoom(),i=this.getMaxZoom();return Math.max(e,Math.min(i,t))}}),n.map=function(t,e){return new n.Map(t,e)},n.Projection.Mercator={MAX_LATITUDE:85.0840591556,R_MINOR:6356752.3142,R_MAJOR:6378137,project:function(t){var e=n.LatLng.DEG_TO_RAD,i=this.MAX_LATITUDE,o=Math.max(Math.min(i,t.lat),-i),s=this.R_MAJOR,a=this.R_MINOR,r=t.lng*e*s,h=o*e,l=a/s,u=Math.sqrt(1-l*l),c=u*Math.sin(h);c=Math.pow((1-c)/(1+c),.5*u);var _=Math.tan(.5*(.5*Math.PI-h))/c;return h=-a*Math.log(_),new n.Point(r,h)},unproject:function(t){for(var e,i=n.LatLng.RAD_TO_DEG,o=this.R_MAJOR,s=this.R_MINOR,a=t.x*i/o,r=s/o,h=Math.sqrt(1-r*r),l=Math.exp(-t.y/s),u=Math.PI/2-2*Math.atan(l),c=15,_=1e-7,d=c,p=.1;Math.abs(p)>_&&--d>0;)e=h*Math.sin(u),p=Math.PI/2-2*Math.atan(l*Math.pow((1-e)/(1+e),.5*h))-u,u+=p;return new n.LatLng(u*i,a)}},n.CRS.EPSG3395=n.extend({},n.CRS,{code:"EPSG:3395",projection:n.Projection.Mercator,transformation:function(){var t=n.Projection.Mercator,e=t.R_MAJOR,i=t.R_MINOR;return new n.Transformation(.5/(Math.PI*e),.5,-.5/(Math.PI*i),.5)}()}),n.TileLayer=n.Class.extend({includes:n.Mixin.Events,options:{minZoom:0,maxZoom:18,tileSize:256,subdomains:"abc",errorTileUrl:"",attribution:"",zoomOffset:0,opacity:1,unloadInvisibleTiles:n.Browser.mobile,updateWhenIdle:n.Browser.mobile},initialize:function(t,e){e=n.setOptions(this,e),e.detectRetina&&n.Browser.retina&&e.maxZoom>0&&(e.tileSize=Math.floor(e.tileSize/2),e.zoomOffset++,e.minZoom>0&&e.minZoom--,this.options.maxZoom--),this._url=t;var i=this.options.subdomains;"string"==typeof i&&(this.options.subdomains=i.split(""))},onAdd:function(t){this._map=t,this._initContainer(),this._createTileProto(),t.on({viewreset:this._resetCallback,moveend:this._update},this),this.options.updateWhenIdle||(this._limitedUpdate=n.Util.limitExecByInterval(this._update,150,this),t.on("move",this._limitedUpdate,this)),this._reset(),this._update()},addTo:function(t){return t.addLayer(this),this},onRemove:function(t){this._container.parentNode.removeChild(this._container),t.off({viewreset:this._resetCallback,moveend:this._update},this),this.options.updateWhenIdle||t.off("move",this._limitedUpdate,this),this._container=null,this._map=null},bringToFront:function(){var t=this._map._panes.tilePane;return this._container&&(t.appendChild(this._container),this._setAutoZIndex(t,Math.max)),this},bringToBack:function(){var t=this._map._panes.tilePane;return this._container&&(t.insertBefore(this._container,t.firstChild),this._setAutoZIndex(t,Math.min)),this},getAttribution:function(){return this.options.attribution},setOpacity:function(t){return this.options.opacity=t,this._map&&this._updateOpacity(),this},setZIndex:function(t){return this.options.zIndex=t,this._updateZIndex(),this},setUrl:function(t,e){return this._url=t,e||this.redraw(),this},redraw:function(){return this._map&&(this._map._panes.tilePane.empty=!1,this._reset(!0),this._update()),this},_updateZIndex:function(){this._container&&this.options.zIndex!==i&&(this._container.style.zIndex=this.options.zIndex)},_setAutoZIndex:function(t,e){var i,n,o,s=t.children,a=-e(1/0,-1/0);for(n=0,o=s.length;o>n;n++)s[n]!==this._container&&(i=parseInt(s[n].style.zIndex,10),isNaN(i)||(a=e(a,i)));this.options.zIndex=this._container.style.zIndex=(isFinite(a)?a:0)+e(1,-1)},_updateOpacity:function(){n.DomUtil.setOpacity(this._container,this.options.opacity);var t,e=this._tiles;if(n.Browser.webkit)for(t in e)e.hasOwnProperty(t)&&(e[t].style.webkitTransform+=" translate(0,0)")},_initContainer:function(){var t=this._map._panes.tilePane;(!this._container||t.empty)&&(this._container=n.DomUtil.create("div","leaflet-layer"),this._updateZIndex(),t.appendChild(this._container),1>this.options.opacity&&this._updateOpacity())},_resetCallback:function(t){this._reset(t.hard)},_reset:function(t){var e=this._tiles;for(var i in e)e.hasOwnProperty(i)&&this.fire("tileunload",{tile:e[i]});this._tiles={},this._tilesToLoad=0,this.options.reuseTiles&&(this._unusedTiles=[]),t&&this._container&&(this._container.innerHTML=""),this._initContainer()},_update:function(){if(this._map){var t=this._map.getPixelBounds(),e=this._map.getZoom(),i=this.options.tileSize;if(!(e>this.options.maxZoom||this.options.minZoom>e)){var o=new n.Point(Math.floor(t.min.x/i),Math.floor(t.min.y/i)),s=new n.Point(Math.floor(t.max.x/i),Math.floor(t.max.y/i)),a=new n.Bounds(o,s);this._addTilesFromCenterOut(a),(this.options.unloadInvisibleTiles||this.options.reuseTiles)&&this._removeOtherTiles(a)}}},_addTilesFromCenterOut:function(t){var i,o,s,a=[],r=t.getCenter();for(i=t.min.y;t.max.y>=i;i++)for(o=t.min.x;t.max.x>=o;o++)s=new n.Point(o,i),this._tileShouldBeLoaded(s)&&a.push(s);var h=a.length;if(0!==h){a.sort(function(t,e){return t.distanceTo(r)-e.distanceTo(r)});var l=e.createDocumentFragment();for(this._tilesToLoad||this.fire("loading"),this._tilesToLoad+=h,o=0;h>o;o++)this._addTile(a[o],l);this._container.appendChild(l)}},_tileShouldBeLoaded:function(t){if(t.x+":"+t.y in this._tiles)return!1;if(!this.options.continuousWorld){var e=this._getWrapTileNum();if(this.options.noWrap&&(0>t.x||t.x>=e)||0>t.y||t.y>=e)return!1}return!0},_removeOtherTiles:function(t){var e,i,n,o;for(o in this._tiles)this._tiles.hasOwnProperty(o)&&(e=o.split(":"),i=parseInt(e[0],10),n=parseInt(e[1],10),(t.min.x>i||i>t.max.x||t.min.y>n||n>t.max.y)&&this._removeTile(o))},_removeTile:function(t){var e=this._tiles[t];this.fire("tileunload",{tile:e,url:e.src}),this.options.reuseTiles?(n.DomUtil.removeClass(e,"leaflet-tile-loaded"),this._unusedTiles.push(e)):e.parentNode===this._container&&this._container.removeChild(e),n.Browser.android||(e.src=n.Util.emptyImageUrl),delete this._tiles[t]},_addTile:function(t,e){var i=this._getTilePos(t),o=this._getTile();n.DomUtil.setPosition(o,i,n.Browser.chrome||n.Browser.android23),this._tiles[t.x+":"+t.y]=o,this._loadTile(o,t),o.parentNode!==this._container&&e.appendChild(o)
+},_getZoomForUrl:function(){var t=this.options,e=this._map.getZoom();return t.zoomReverse&&(e=t.maxZoom-e),e+t.zoomOffset},_getTilePos:function(t){var e=this._map.getPixelOrigin(),i=this.options.tileSize;return t.multiplyBy(i).subtract(e)},getTileUrl:function(t){return this._adjustTilePoint(t),n.Util.template(this._url,n.extend({s:this._getSubdomain(t),z:this._getZoomForUrl(),x:t.x,y:t.y},this.options))},_getWrapTileNum:function(){return Math.pow(2,this._getZoomForUrl())},_adjustTilePoint:function(t){var e=this._getWrapTileNum();this.options.continuousWorld||this.options.noWrap||(t.x=(t.x%e+e)%e),this.options.tms&&(t.y=e-t.y-1)},_getSubdomain:function(t){var e=(t.x+t.y)%this.options.subdomains.length;return this.options.subdomains[e]},_createTileProto:function(){var t=this._tileImg=n.DomUtil.create("img","leaflet-tile");t.style.width=t.style.height=this.options.tileSize+"px",t.galleryimg="no"},_getTile:function(){if(this.options.reuseTiles&&this._unusedTiles.length>0){var t=this._unusedTiles.pop();return this._resetTile(t),t}return this._createTile()},_resetTile:function(){},_createTile:function(){var t=this._tileImg.cloneNode(!1);return t.onselectstart=t.onmousemove=n.Util.falseFn,t},_loadTile:function(t,e){t._layer=this,t.onload=this._tileOnLoad,t.onerror=this._tileOnError,t.src=this.getTileUrl(e)},_tileLoaded:function(){this._tilesToLoad--,this._tilesToLoad||this.fire("load")},_tileOnLoad:function(){var t=this._layer;this.src!==n.Util.emptyImageUrl&&(n.DomUtil.addClass(this,"leaflet-tile-loaded"),t.fire("tileload",{tile:this,url:this.src})),t._tileLoaded()},_tileOnError:function(){var t=this._layer;t.fire("tileerror",{tile:this,url:this.src});var e=t.options.errorTileUrl;e&&(this.src=e),t._tileLoaded()}}),n.tileLayer=function(t,e){return new n.TileLayer(t,e)},n.TileLayer.WMS=n.TileLayer.extend({defaultWmsParams:{service:"WMS",request:"GetMap",version:"1.1.1",layers:"",styles:"",format:"image/jpeg",transparent:!1},initialize:function(t,e){this._url=t;var i=n.extend({},this.defaultWmsParams);i.width=i.height=e.detectRetina&&n.Browser.retina?2*this.options.tileSize:this.options.tileSize;for(var o in e)this.options.hasOwnProperty(o)||(i[o]=e[o]);this.wmsParams=i,n.setOptions(this,e)},onAdd:function(t){var e=parseFloat(this.wmsParams.version)>=1.3?"crs":"srs";this.wmsParams[e]=t.options.crs.code,n.TileLayer.prototype.onAdd.call(this,t)},getTileUrl:function(t,e){this._adjustTilePoint(t);var i=this._map,o=i.options.crs,s=this.options.tileSize,a=t.multiplyBy(s),r=a.add(new n.Point(s,s)),h=o.project(i.unproject(a,e)),l=o.project(i.unproject(r,e)),u=[h.x,l.y,l.x,h.y].join(","),c=n.Util.template(this._url,{s:this._getSubdomain(t)});return c+n.Util.getParamString(this.wmsParams,c)+"&bbox="+u},setParams:function(t,e){return n.extend(this.wmsParams,t),e||this.redraw(),this}}),n.tileLayer.wms=function(t,e){return new n.TileLayer.WMS(t,e)},n.TileLayer.Canvas=n.TileLayer.extend({options:{async:!1},initialize:function(t){n.setOptions(this,t)},redraw:function(){var t=this._tiles;for(var e in t)t.hasOwnProperty(e)&&this._redrawTile(t[e])},_redrawTile:function(t){this.drawTile(t,t._tilePoint,this._map._zoom)},_createTileProto:function(){var t=this._canvasProto=n.DomUtil.create("canvas","leaflet-tile");t.width=t.height=this.options.tileSize},_createTile:function(){var t=this._canvasProto.cloneNode(!1);return t.onselectstart=t.onmousemove=n.Util.falseFn,t},_loadTile:function(t,e){t._layer=this,t._tilePoint=e,this._redrawTile(t),this.options.async||this.tileDrawn(t)},drawTile:function(){},tileDrawn:function(t){this._tileOnLoad.call(t)}}),n.tileLayer.canvas=function(t){return new n.TileLayer.Canvas(t)},n.ImageOverlay=n.Class.extend({includes:n.Mixin.Events,options:{opacity:1},initialize:function(t,e,i){this._url=t,this._bounds=n.latLngBounds(e),n.setOptions(this,i)},onAdd:function(t){this._map=t,this._image||this._initImage(),t._panes.overlayPane.appendChild(this._image),t.on("viewreset",this._reset,this),t.options.zoomAnimation&&n.Browser.any3d&&t.on("zoomanim",this._animateZoom,this),this._reset()},onRemove:function(t){t.getPanes().overlayPane.removeChild(this._image),t.off("viewreset",this._reset,this),t.options.zoomAnimation&&t.off("zoomanim",this._animateZoom,this)},addTo:function(t){return t.addLayer(this),this},setOpacity:function(t){return this.options.opacity=t,this._updateOpacity(),this},bringToFront:function(){return this._image&&this._map._panes.overlayPane.appendChild(this._image),this},bringToBack:function(){var t=this._map._panes.overlayPane;return this._image&&t.insertBefore(this._image,t.firstChild),this},_initImage:function(){this._image=n.DomUtil.create("img","leaflet-image-layer"),this._map.options.zoomAnimation&&n.Browser.any3d?n.DomUtil.addClass(this._image,"leaflet-zoom-animated"):n.DomUtil.addClass(this._image,"leaflet-zoom-hide"),this._updateOpacity(),n.extend(this._image,{galleryimg:"no",onselectstart:n.Util.falseFn,onmousemove:n.Util.falseFn,onload:n.bind(this._onImageLoad,this),src:this._url})},_animateZoom:function(t){var e=this._map,i=this._image,o=e.getZoomScale(t.zoom),s=this._bounds.getNorthWest(),a=this._bounds.getSouthEast(),r=e._latLngToNewLayerPoint(s,t.zoom,t.center),h=e._latLngToNewLayerPoint(a,t.zoom,t.center)._subtract(r),l=r._add(h._multiplyBy(.5*(1-1/o)));i.style[n.DomUtil.TRANSFORM]=n.DomUtil.getTranslateString(l)+" scale("+o+") "},_reset:function(){var t=this._image,e=this._map.latLngToLayerPoint(this._bounds.getNorthWest()),i=this._map.latLngToLayerPoint(this._bounds.getSouthEast())._subtract(e);n.DomUtil.setPosition(t,e),t.style.width=i.x+"px",t.style.height=i.y+"px"},_onImageLoad:function(){this.fire("load")},_updateOpacity:function(){n.DomUtil.setOpacity(this._image,this.options.opacity)}}),n.imageOverlay=function(t,e,i){return new n.ImageOverlay(t,e,i)},n.Icon=n.Class.extend({options:{className:""},initialize:function(t){n.setOptions(this,t)},createIcon:function(){return this._createIcon("icon")},createShadow:function(){return this._createIcon("shadow")},_createIcon:function(t){var e=this._getIconUrl(t);if(!e){if("icon"===t)throw Error("iconUrl not set in Icon options (see the docs).");return null}var i=this._createImg(e);return this._setIconStyles(i,t),i},_setIconStyles:function(t,e){var i,o=this.options,s=n.point(o[e+"Size"]);i="shadow"===e?n.point(o.shadowAnchor||o.iconAnchor):n.point(o.iconAnchor),!i&&s&&(i=s.divideBy(2,!0)),t.className="leaflet-marker-"+e+" "+o.className,i&&(t.style.marginLeft=-i.x+"px",t.style.marginTop=-i.y+"px"),s&&(t.style.width=s.x+"px",t.style.height=s.y+"px")},_createImg:function(t){var i;return n.Browser.ie6?(i=e.createElement("div"),i.style.filter='progid:DXImageTransform.Microsoft.AlphaImageLoader(src="'+t+'")'):(i=e.createElement("img"),i.src=t),i},_getIconUrl:function(t){return n.Browser.retina&&this.options[t+"RetinaUrl"]?this.options[t+"RetinaUrl"]:this.options[t+"Url"]}}),n.icon=function(t){return new n.Icon(t)},n.Icon.Default=n.Icon.extend({options:{iconSize:new n.Point(25,41),iconAnchor:new n.Point(12,41),popupAnchor:new n.Point(1,-34),shadowSize:new n.Point(41,41)},_getIconUrl:function(t){var e=t+"Url";if(this.options[e])return this.options[e];n.Browser.retina&&"icon"===t&&(t+="@2x");var i=n.Icon.Default.imagePath;if(!i)throw Error("Couldn't autodetect L.Icon.Default.imagePath, set it manually.");return i+"/marker-"+t+".png"}}),n.Icon.Default.imagePath=function(){var t,i,n,o,s=e.getElementsByTagName("script"),a=/\/?leaflet[\-\._]?([\w\-\._]*)\.js\??/;for(t=0,i=s.length;i>t;t++)if(n=s[t].src,o=n.match(a))return n.split(a)[0]+"/images"}(),n.Marker=n.Class.extend({includes:n.Mixin.Events,options:{icon:new n.Icon.Default,title:"",clickable:!0,draggable:!1,zIndexOffset:0,opacity:1,riseOnHover:!1,riseOffset:250},initialize:function(t,e){n.setOptions(this,e),this._latlng=n.latLng(t)},onAdd:function(t){this._map=t,t.on("viewreset",this.update,this),this._initIcon(),this.update(),t.options.zoomAnimation&&t.options.markerZoomAnimation&&t.on("zoomanim",this._animateZoom,this)},addTo:function(t){return t.addLayer(this),this},onRemove:function(t){this._removeIcon(),this.fire("remove"),t.off({viewreset:this.update,zoomanim:this._animateZoom},this),this._map=null},getLatLng:function(){return this._latlng},setLatLng:function(t){return this._latlng=n.latLng(t),this.update(),this.fire("move",{latlng:this._latlng})},setZIndexOffset:function(t){return this.options.zIndexOffset=t,this.update(),this},setIcon:function(t){return this._map&&this._removeIcon(),this.options.icon=t,this._map&&(this._initIcon(),this.update()),this},update:function(){if(this._icon){var t=this._map.latLngToLayerPoint(this._latlng).round();this._setPos(t)}return this},_initIcon:function(){var t=this.options,e=this._map,i=e.options.zoomAnimation&&e.options.markerZoomAnimation,o=i?"leaflet-zoom-animated":"leaflet-zoom-hide",s=!1;this._icon||(this._icon=t.icon.createIcon(),t.title&&(this._icon.title=t.title),this._initInteraction(),s=1>this.options.opacity,n.DomUtil.addClass(this._icon,o),t.riseOnHover&&n.DomEvent.on(this._icon,"mouseover",this._bringToFront,this).on(this._icon,"mouseout",this._resetZIndex,this)),this._shadow||(this._shadow=t.icon.createShadow(),this._shadow&&(n.DomUtil.addClass(this._shadow,o),s=1>this.options.opacity)),s&&this._updateOpacity();var a=this._map._panes;a.markerPane.appendChild(this._icon),this._shadow&&a.shadowPane.appendChild(this._shadow)},_removeIcon:function(){var t=this._map._panes;this.options.riseOnHover&&n.DomEvent.off(this._icon,"mouseover",this._bringToFront).off(this._icon,"mouseout",this._resetZIndex),t.markerPane.removeChild(this._icon),this._shadow&&t.shadowPane.removeChild(this._shadow),this._icon=this._shadow=null},_setPos:function(t){n.DomUtil.setPosition(this._icon,t),this._shadow&&n.DomUtil.setPosition(this._shadow,t),this._zIndex=t.y+this.options.zIndexOffset,this._resetZIndex()},_updateZIndex:function(t){this._icon.style.zIndex=this._zIndex+t},_animateZoom:function(t){var e=this._map._latLngToNewLayerPoint(this._latlng,t.zoom,t.center);this._setPos(e)},_initInteraction:function(){if(this.options.clickable){var t=this._icon,e=["dblclick","mousedown","mouseover","mouseout","contextmenu"];n.DomUtil.addClass(t,"leaflet-clickable"),n.DomEvent.on(t,"click",this._onMouseClick,this);for(var i=0;e.length>i;i++)n.DomEvent.on(t,e[i],this._fireMouseEvent,this);n.Handler.MarkerDrag&&(this.dragging=new n.Handler.MarkerDrag(this),this.options.draggable&&this.dragging.enable())}},_onMouseClick:function(t){var e=this.dragging&&this.dragging.moved();(this.hasEventListeners(t.type)||e)&&n.DomEvent.stopPropagation(t),e||(this.dragging&&this.dragging._enabled||!this._map.dragging||!this._map.dragging.moved())&&this.fire(t.type,{originalEvent:t})},_fireMouseEvent:function(t){this.fire(t.type,{originalEvent:t}),"contextmenu"===t.type&&this.hasEventListeners(t.type)&&n.DomEvent.preventDefault(t),"mousedown"!==t.type&&n.DomEvent.stopPropagation(t)},setOpacity:function(t){this.options.opacity=t,this._map&&this._updateOpacity()},_updateOpacity:function(){n.DomUtil.setOpacity(this._icon,this.options.opacity),this._shadow&&n.DomUtil.setOpacity(this._shadow,this.options.opacity)},_bringToFront:function(){this._updateZIndex(this.options.riseOffset)},_resetZIndex:function(){this._updateZIndex(0)}}),n.marker=function(t,e){return new n.Marker(t,e)},n.DivIcon=n.Icon.extend({options:{iconSize:new n.Point(12,12),className:"leaflet-div-icon"},createIcon:function(){var t=e.createElement("div"),i=this.options;return i.html&&(t.innerHTML=i.html),i.bgPos&&(t.style.backgroundPosition=-i.bgPos.x+"px "+-i.bgPos.y+"px"),this._setIconStyles(t,"icon"),t},createShadow:function(){return null}}),n.divIcon=function(t){return new n.DivIcon(t)},n.Map.mergeOptions({closePopupOnClick:!0}),n.Popup=n.Class.extend({includes:n.Mixin.Events,options:{minWidth:50,maxWidth:300,maxHeight:null,autoPan:!0,closeButton:!0,offset:new n.Point(0,6),autoPanPadding:new n.Point(5,5),className:"",zoomAnimation:!0},initialize:function(t,e){n.setOptions(this,t),this._source=e,this._animated=n.Browser.any3d&&this.options.zoomAnimation},onAdd:function(t){this._map=t,this._container||this._initLayout(),this._updateContent();var e=t.options.fadeAnimation;e&&n.DomUtil.setOpacity(this._container,0),t._panes.popupPane.appendChild(this._container),t.on("viewreset",this._updatePosition,this),this._animated&&t.on("zoomanim",this._zoomAnimation,this),t.options.closePopupOnClick&&t.on("preclick",this._close,this),this._update(),e&&n.DomUtil.setOpacity(this._container,1)},addTo:function(t){return t.addLayer(this),this},openOn:function(t){return t.openPopup(this),this},onRemove:function(t){t._panes.popupPane.removeChild(this._container),n.Util.falseFn(this._container.offsetWidth),t.off({viewreset:this._updatePosition,preclick:this._close,zoomanim:this._zoomAnimation},this),t.options.fadeAnimation&&n.DomUtil.setOpacity(this._container,0),this._map=null},setLatLng:function(t){return this._latlng=n.latLng(t),this._update(),this},setContent:function(t){return this._content=t,this._update(),this},_close:function(){var t=this._map;t&&(t._popup=null,t.removeLayer(this).fire("popupclose",{popup:this}))},_initLayout:function(){var t,e="leaflet-popup",i=e+" "+this.options.className+" leaflet-zoom-"+(this._animated?"animated":"hide"),o=this._container=n.DomUtil.create("div",i);this.options.closeButton&&(t=this._closeButton=n.DomUtil.create("a",e+"-close-button",o),t.href="#close",t.innerHTML="&#215;",n.DomEvent.on(t,"click",this._onCloseButtonClick,this));var s=this._wrapper=n.DomUtil.create("div",e+"-content-wrapper",o);n.DomEvent.disableClickPropagation(s),this._contentNode=n.DomUtil.create("div",e+"-content",s),n.DomEvent.on(this._contentNode,"mousewheel",n.DomEvent.stopPropagation),this._tipContainer=n.DomUtil.create("div",e+"-tip-container",o),this._tip=n.DomUtil.create("div",e+"-tip",this._tipContainer)},_update:function(){this._map&&(this._container.style.visibility="hidden",this._updateContent(),this._updateLayout(),this._updatePosition(),this._container.style.visibility="",this._adjustPan())},_updateContent:function(){if(this._content){if("string"==typeof this._content)this._contentNode.innerHTML=this._content;else{for(;this._contentNode.hasChildNodes();)this._contentNode.removeChild(this._contentNode.firstChild);this._contentNode.appendChild(this._content)}this.fire("contentupdate")}},_updateLayout:function(){var t=this._contentNode,e=t.style;e.width="",e.whiteSpace="nowrap";var i=t.offsetWidth;i=Math.min(i,this.options.maxWidth),i=Math.max(i,this.options.minWidth),e.width=i+1+"px",e.whiteSpace="",e.height="";var o=t.offsetHeight,s=this.options.maxHeight,a="leaflet-popup-scrolled";s&&o>s?(e.height=s+"px",n.DomUtil.addClass(t,a)):n.DomUtil.removeClass(t,a),this._containerWidth=this._container.offsetWidth},_updatePosition:function(){if(this._map){var t=this._map.latLngToLayerPoint(this._latlng),e=this._animated,i=this.options.offset;e&&n.DomUtil.setPosition(this._container,t),this._containerBottom=-i.y-(e?0:t.y),this._containerLeft=-Math.round(this._containerWidth/2)+i.x+(e?0:t.x),this._container.style.bottom=this._containerBottom+"px",this._container.style.left=this._containerLeft+"px"}},_zoomAnimation:function(t){var e=this._map._latLngToNewLayerPoint(this._latlng,t.zoom,t.center);n.DomUtil.setPosition(this._container,e)},_adjustPan:function(){if(this.options.autoPan){var t=this._map,e=this._container.offsetHeight,i=this._containerWidth,o=new n.Point(this._containerLeft,-e-this._containerBottom);this._animated&&o._add(n.DomUtil.getPosition(this._container));var s=t.layerPointToContainerPoint(o),a=this.options.autoPanPadding,r=t.getSize(),h=0,l=0;0>s.x&&(h=s.x-a.x),s.x+i>r.x&&(h=s.x+i-r.x+a.x),0>s.y&&(l=s.y-a.y),s.y+e>r.y&&(l=s.y+e-r.y+a.y),(h||l)&&t.panBy(new n.Point(h,l))}},_onCloseButtonClick:function(t){this._close(),n.DomEvent.stop(t)}}),n.popup=function(t,e){return new n.Popup(t,e)},n.Marker.include({openPopup:function(){return this._popup&&this._map&&(this._popup.setLatLng(this._latlng),this._map.openPopup(this._popup)),this},closePopup:function(){return this._popup&&this._popup._close(),this},bindPopup:function(t,e){var i=n.point(this.options.icon.options.popupAnchor)||new n.Point(0,0);return i=i.add(n.Popup.prototype.options.offset),e&&e.offset&&(i=i.add(e.offset)),e=n.extend({offset:i},e),this._popup||this.on("click",this.openPopup,this).on("remove",this.closePopup,this).on("move",this._movePopup,this),this._popup=new n.Popup(e,this).setContent(t),this},unbindPopup:function(){return this._popup&&(this._popup=null,this.off("click",this.openPopup).off("remove",this.closePopup).off("move",this._movePopup)),this},_movePopup:function(t){this._popup.setLatLng(t.latlng)}}),n.Map.include({openPopup:function(t){return this.closePopup(),this._popup=t,this.addLayer(t).fire("popupopen",{popup:this._popup})},closePopup:function(){return this._popup&&this._popup._close(),this}}),n.LayerGroup=n.Class.extend({initialize:function(t){this._layers={};var e,i;if(t)for(e=0,i=t.length;i>e;e++)this.addLayer(t[e])},addLayer:function(t){var e=n.stamp(t);return this._layers[e]=t,this._map&&this._map.addLayer(t),this},removeLayer:function(t){var e=n.stamp(t);return delete this._layers[e],this._map&&this._map.removeLayer(t),this},clearLayers:function(){return this.eachLayer(this.removeLayer,this),this},invoke:function(t){var e,i,n=Array.prototype.slice.call(arguments,1);for(e in this._layers)this._layers.hasOwnProperty(e)&&(i=this._layers[e],i[t]&&i[t].apply(i,n));return this},onAdd:function(t){this._map=t,this.eachLayer(t.addLayer,t)},onRemove:function(t){this.eachLayer(t.removeLayer,t),this._map=null},addTo:function(t){return t.addLayer(this),this},eachLayer:function(t,e){for(var i in this._layers)this._layers.hasOwnProperty(i)&&t.call(e,this._layers[i])},setZIndex:function(t){return this.invoke("setZIndex",t)}}),n.layerGroup=function(t){return new n.LayerGroup(t)},n.FeatureGroup=n.LayerGroup.extend({includes:n.Mixin.Events,statics:{EVENTS:"click dblclick mouseover mouseout mousemove contextmenu"},addLayer:function(t){return this._layers[n.stamp(t)]?this:(t.on(n.FeatureGroup.EVENTS,this._propagateEvent,this),n.LayerGroup.prototype.addLayer.call(this,t),this._popupContent&&t.bindPopup&&t.bindPopup(this._popupContent,this._popupOptions),this.fire("layeradd",{layer:t}))},removeLayer:function(t){return t.off(n.FeatureGroup.EVENTS,this._propagateEvent,this),n.LayerGroup.prototype.removeLayer.call(this,t),this._popupContent&&this.invoke("unbindPopup"),this.fire("layerremove",{layer:t})},bindPopup:function(t,e){return this._popupContent=t,this._popupOptions=e,this.invoke("bindPopup",t,e)},setStyle:function(t){return this.invoke("setStyle",t)},bringToFront:function(){return this.invoke("bringToFront")},bringToBack:function(){return this.invoke("bringToBack")},getBounds:function(){var t=new n.LatLngBounds;return this.eachLayer(function(e){t.extend(e instanceof n.Marker?e.getLatLng():e.getBounds())}),t},_propagateEvent:function(t){t.layer=t.target,t.target=this,this.fire(t.type,t)}}),n.featureGroup=function(t){return new n.FeatureGroup(t)},n.Path=n.Class.extend({includes:[n.Mixin.Events],statics:{CLIP_PADDING:n.Browser.mobile?Math.max(0,Math.min(.5,(1280/Math.max(t.innerWidth,t.innerHeight)-1)/2)):.5},options:{stroke:!0,color:"#0033ff",dashArray:null,weight:5,opacity:.5,fill:!1,fillColor:null,fillOpacity:.2,clickable:!0},initialize:function(t){n.setOptions(this,t)},onAdd:function(t){this._map=t,this._container||(this._initElements(),this._initEvents()),this.projectLatlngs(),this._updatePath(),this._container&&this._map._pathRoot.appendChild(this._container),this.fire("add"),t.on({viewreset:this.projectLatlngs,moveend:this._updatePath},this)},addTo:function(t){return t.addLayer(this),this},onRemove:function(t){t._pathRoot.removeChild(this._container),this.fire("remove"),this._map=null,n.Browser.vml&&(this._container=null,this._stroke=null,this._fill=null),t.off({viewreset:this.projectLatlngs,moveend:this._updatePath},this)},projectLatlngs:function(){},setStyle:function(t){return n.setOptions(this,t),this._container&&this._updateStyle(),this},redraw:function(){return this._map&&(this.projectLatlngs(),this._updatePath()),this}}),n.Map.include({_updatePathViewport:function(){var t=n.Path.CLIP_PADDING,e=this.getSize(),i=n.DomUtil.getPosition(this._mapPane),o=i.multiplyBy(-1)._subtract(e.multiplyBy(t)._round()),s=o.add(e.multiplyBy(1+2*t)._round());this._pathViewport=new n.Bounds(o,s)}}),n.Path.SVG_NS="http://www.w3.org/2000/svg",n.Browser.svg=!(!e.createElementNS||!e.createElementNS(n.Path.SVG_NS,"svg").createSVGRect),n.Path=n.Path.extend({statics:{SVG:n.Browser.svg},bringToFront:function(){var t=this._map._pathRoot,e=this._container;return e&&t.lastChild!==e&&t.appendChild(e),this},bringToBack:function(){var t=this._map._pathRoot,e=this._container,i=t.firstChild;return e&&i!==e&&t.insertBefore(e,i),this},getPathString:function(){},_createElement:function(t){return e.createElementNS(n.Path.SVG_NS,t)},_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(){this.options.stroke&&(this._path.setAttribute("stroke-linejoin","round"),this._path.setAttribute("stroke-linecap","round")),this.options.fill&&this._path.setAttribute("fill-rule","evenodd"),this._updateStyle()},_updateStyle:function(){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),this.options.dashArray?this._path.setAttribute("stroke-dasharray",this.options.dashArray):this._path.removeAttribute("stroke-dasharray")):this._path.setAttribute("stroke","none"),this.options.fill?(this._path.setAttribute("fill",this.options.fillColor||this.options.color),this._path.setAttribute("fill-opacity",this.options.fillOpacity)):this._path.setAttribute("fill","none")},_updatePath:function(){var t=this.getPathString();t||(t="M0 0"),this._path.setAttribute("d",t)},_initEvents:function(){if(this.options.clickable){(n.Browser.svg||!n.Browser.vml)&&this._path.setAttribute("class","leaflet-clickable"),n.DomEvent.on(this._container,"click",this._onMouseClick,this);for(var t=["dblclick","mousedown","mouseover","mouseout","mousemove","contextmenu"],e=0;t.length>e;e++)n.DomEvent.on(this._container,t[e],this._fireMouseEvent,this)}},_onMouseClick:function(t){this._map.dragging&&this._map.dragging.moved()||this._fireMouseEvent(t)},_fireMouseEvent:function(t){if(this.hasEventListeners(t.type)){var e=this._map,i=e.mouseEventToContainerPoint(t),o=e.containerPointToLayerPoint(i),s=e.layerPointToLatLng(o);this.fire(t.type,{latlng:s,layerPoint:o,containerPoint:i,originalEvent:t}),"contextmenu"===t.type&&n.DomEvent.preventDefault(t),"mousemove"!==t.type&&n.DomEvent.stopPropagation(t)}}}),n.Map.include({_initPathRoot:function(){this._pathRoot||(this._pathRoot=n.Path.prototype._createElement("svg"),this._panes.overlayPane.appendChild(this._pathRoot),this.options.zoomAnimation&&n.Browser.any3d?(this._pathRoot.setAttribute("class"," leaflet-zoom-animated"),this.on({zoomanim:this._animatePathZoom,zoomend:this._endPathZoom})):this._pathRoot.setAttribute("class"," leaflet-zoom-hide"),this.on("moveend",this._updateSvgViewport),this._updateSvgViewport())},_animatePathZoom:function(t){var e=this.getZoomScale(t.zoom),i=this._getCenterOffset(t.center)._multiplyBy(-e)._add(this._pathViewport.min);this._pathRoot.style[n.DomUtil.TRANSFORM]=n.DomUtil.getTranslateString(i)+" scale("+e+") ",this._pathZooming=!0},_endPathZoom:function(){this._pathZooming=!1},_updateSvgViewport:function(){if(!this._pathZooming){this._updatePathViewport();var t=this._pathViewport,e=t.min,i=t.max,o=i.x-e.x,s=i.y-e.y,a=this._pathRoot,r=this._panes.overlayPane;n.Browser.mobileWebkit&&r.removeChild(a),n.DomUtil.setPosition(a,e),a.setAttribute("width",o),a.setAttribute("height",s),a.setAttribute("viewBox",[e.x,e.y,o,s].join(" ")),n.Browser.mobileWebkit&&r.appendChild(a)}}}),n.Path.include({bindPopup:function(t,e){return(!this._popup||e)&&(this._popup=new n.Popup(e,this)),this._popup.setContent(t),this._popupHandlersAdded||(this.on("click",this._openPopup,this).on("remove",this.closePopup,this),this._popupHandlersAdded=!0),this},unbindPopup:function(){return this._popup&&(this._popup=null,this.off("click",this._openPopup).off("remove",this.closePopup),this._popupHandlersAdded=!1),this},openPopup:function(t){return this._popup&&(t=t||this._latlng||this._latlngs[Math.floor(this._latlngs.length/2)],this._openPopup({latlng:t})),this},closePopup:function(){return this._popup&&this._popup._close(),this},_openPopup:function(t){this._popup.setLatLng(t.latlng),this._map.openPopup(this._popup)}}),n.Browser.vml=!n.Browser.svg&&function(){try{var t=e.createElement("div");t.innerHTML='<v:shape adj="1"/>';var i=t.firstChild;return i.style.behavior="url(#default#VML)",i&&"object"==typeof i.adj}catch(n){return!1}}(),n.Path=n.Browser.svg||!n.Browser.vml?n.Path:n.Path.extend({statics:{VML:!0,CLIP_PADDING:.02},_createElement:function(){try{return e.namespaces.add("lvml","urn:schemas-microsoft-com:vml"),function(t){return e.createElement("<lvml:"+t+' class="lvml">')}}catch(t){return function(t){return e.createElement("<"+t+' xmlns="urn:schemas-microsoft.com:vml" class="lvml">')}}}(),_initPath:function(){var t=this._container=this._createElement("shape");n.DomUtil.addClass(t,"leaflet-vml-shape"),this.options.clickable&&n.DomUtil.addClass(t,"leaflet-clickable"),t.coordsize="1 1",this._path=this._createElement("path"),t.appendChild(this._path),this._map._pathRoot.appendChild(t)},_initStyle:function(){this._updateStyle()},_updateStyle:function(){var t=this._stroke,e=this._fill,i=this.options,n=this._container;n.stroked=i.stroke,n.filled=i.fill,i.stroke?(t||(t=this._stroke=this._createElement("stroke"),t.endcap="round",n.appendChild(t)),t.weight=i.weight+"px",t.color=i.color,t.opacity=i.opacity,t.dashStyle=i.dashArray?i.dashArray instanceof Array?i.dashArray.join(" "):i.dashArray.replace(/ *, */g," "):""):t&&(n.removeChild(t),this._stroke=null),i.fill?(e||(e=this._fill=this._createElement("fill"),n.appendChild(e)),e.color=i.fillColor||i.color,e.opacity=i.fillOpacity):e&&(n.removeChild(e),this._fill=null)},_updatePath:function(){var t=this._container.style;t.display="none",this._path.v=this.getPathString()+" ",t.display=""}}),n.Map.include(n.Browser.svg||!n.Browser.vml?{}:{_initPathRoot:function(){if(!this._pathRoot){var t=this._pathRoot=e.createElement("div");t.className="leaflet-vml-container",this._panes.overlayPane.appendChild(t),this.on("moveend",this._updatePathViewport),this._updatePathViewport()}}}),n.Browser.canvas=function(){return!!e.createElement("canvas").getContext}(),n.Path=n.Path.SVG&&!t.L_PREFER_CANVAS||!n.Browser.canvas?n.Path:n.Path.extend({statics:{CANVAS:!0,SVG:!1},redraw:function(){return this._map&&(this.projectLatlngs(),this._requestUpdate()),this},setStyle:function(t){return n.setOptions(this,t),this._map&&(this._updateStyle(),this._requestUpdate()),this},onRemove:function(t){t.off("viewreset",this.projectLatlngs,this).off("moveend",this._updatePath,this),this.options.clickable&&this._map.off("click",this._onClick,this),this._requestUpdate(),this._map=null},_requestUpdate:function(){this._map&&!n.Path._updateRequest&&(n.Path._updateRequest=n.Util.requestAnimFrame(this._fireMapMoveEnd,this._map))},_fireMapMoveEnd:function(){n.Path._updateRequest=null,this.fire("moveend")},_initElements:function(){this._map._initPathRoot(),this._ctx=this._map._canvasCtx},_updateStyle:function(){var t=this.options;t.stroke&&(this._ctx.lineWidth=t.weight,this._ctx.strokeStyle=t.color),t.fill&&(this._ctx.fillStyle=t.fillColor||t.color)},_drawPath:function(){var t,e,i,o,s,a;for(this._ctx.beginPath(),t=0,i=this._parts.length;i>t;t++){for(e=0,o=this._parts[t].length;o>e;e++)s=this._parts[t][e],a=(0===e?"move":"line")+"To",this._ctx[a](s.x,s.y);this instanceof n.Polygon&&this._ctx.closePath()}},_checkIfEmpty:function(){return!this._parts.length},_updatePath:function(){if(!this._checkIfEmpty()){var t=this._ctx,e=this.options;this._drawPath(),t.save(),this._updateStyle(),e.fill&&(t.globalAlpha=e.fillOpacity,t.fill()),e.stroke&&(t.globalAlpha=e.opacity,t.stroke()),t.restore()}},_initEvents:function(){this.options.clickable&&this._map.on("click",this._onClick,this)},_onClick:function(t){this._containsPoint(t.layerPoint)&&this.fire("click",{latlng:t.latlng,layerPoint:t.layerPoint,containerPoint:t.containerPoint,originalEvent:t})}}),n.Map.include(n.Path.SVG&&!t.L_PREFER_CANVAS||!n.Browser.canvas?{}:{_initPathRoot:function(){var t,i=this._pathRoot;i||(i=this._pathRoot=e.createElement("canvas"),i.style.position="absolute",t=this._canvasCtx=i.getContext("2d"),t.lineCap="round",t.lineJoin="round",this._panes.overlayPane.appendChild(i),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(){if(!this._pathZooming){this._updatePathViewport();var t=this._pathViewport,e=t.min,i=t.max.subtract(e),o=this._pathRoot;n.DomUtil.setPosition(o,e),o.width=i.x,o.height=i.y,o.getContext("2d").translate(-e.x,-e.y)}}}),n.LineUtil={simplify:function(t,e){if(!e||!t.length)return t.slice();var i=e*e;return t=this._reducePoints(t,i),t=this._simplifyDP(t,i)},pointToSegmentDistance:function(t,e,i){return Math.sqrt(this._sqClosestPointOnSegment(t,e,i,!0))},closestPointOnSegment:function(t,e,i){return this._sqClosestPointOnSegment(t,e,i)},_simplifyDP:function(t,e){var n=t.length,o=typeof Uint8Array!=i+""?Uint8Array:Array,s=new o(n);s[0]=s[n-1]=1,this._simplifyDPStep(t,s,e,0,n-1);var a,r=[];for(a=0;n>a;a++)s[a]&&r.push(t[a]);return r},_simplifyDPStep:function(t,e,i,n,o){var s,a,r,h=0;for(a=n+1;o-1>=a;a++)r=this._sqClosestPointOnSegment(t[a],t[n],t[o],!0),r>h&&(s=a,h=r);h>i&&(e[s]=1,this._simplifyDPStep(t,e,i,n,s),this._simplifyDPStep(t,e,i,s,o))},_reducePoints:function(t,e){for(var i=[t[0]],n=1,o=0,s=t.length;s>n;n++)this._sqDist(t[n],t[o])>e&&(i.push(t[n]),o=n);return s-1>o&&i.push(t[s-1]),i},clipSegment:function(t,e,i,n){var o,s,a,r=n?this._lastCode:this._getBitCode(t,i),h=this._getBitCode(e,i);for(this._lastCode=h;;){if(!(r|h))return[t,e];if(r&h)return!1;o=r||h,s=this._getEdgeIntersection(t,e,o,i),a=this._getBitCode(s,i),o===r?(t=s,r=a):(e=s,h=a)}},_getEdgeIntersection:function(t,e,o,s){var a=e.x-t.x,r=e.y-t.y,h=s.min,l=s.max;return 8&o?new n.Point(t.x+a*(l.y-t.y)/r,l.y):4&o?new n.Point(t.x+a*(h.y-t.y)/r,h.y):2&o?new n.Point(l.x,t.y+r*(l.x-t.x)/a):1&o?new n.Point(h.x,t.y+r*(h.x-t.x)/a):i},_getBitCode:function(t,e){var i=0;return t.x<e.min.x?i|=1:t.x>e.max.x&&(i|=2),t.y<e.min.y?i|=4:t.y>e.max.y&&(i|=8),i},_sqDist:function(t,e){var i=e.x-t.x,n=e.y-t.y;return i*i+n*n},_sqClosestPointOnSegment:function(t,e,i,o){var s,a=e.x,r=e.y,h=i.x-a,l=i.y-r,u=h*h+l*l;return u>0&&(s=((t.x-a)*h+(t.y-r)*l)/u,s>1?(a=i.x,r=i.y):s>0&&(a+=h*s,r+=l*s)),h=t.x-a,l=t.y-r,o?h*h+l*l:new n.Point(a,r)}},n.Polyline=n.Path.extend({initialize:function(t,e){n.Path.prototype.initialize.call(this,e),this._latlngs=this._convertLatLngs(t)},options:{smoothFactor:1,noClip:!1},projectLatlngs:function(){this._originalPoints=[];for(var t=0,e=this._latlngs.length;e>t;t++)this._originalPoints[t]=this._map.latLngToLayerPoint(this._latlngs[t])},getPathString:function(){for(var t=0,e=this._parts.length,i="";e>t;t++)i+=this._getPathPartStr(this._parts[t]);return i},getLatLngs:function(){return this._latlngs},setLatLngs:function(t){return this._latlngs=this._convertLatLngs(t),this.redraw()},addLatLng:function(t){return this._latlngs.push(n.latLng(t)),this.redraw()},spliceLatLngs:function(){var t=[].splice.apply(this._latlngs,arguments);return this._convertLatLngs(this._latlngs),this.redraw(),t},closestLayerPoint:function(t){for(var e,i,o=1/0,s=this._parts,a=null,r=0,h=s.length;h>r;r++)for(var l=s[r],u=1,c=l.length;c>u;u++){e=l[u-1],i=l[u];
+var _=n.LineUtil._sqClosestPointOnSegment(t,e,i,!0);o>_&&(o=_,a=n.LineUtil._sqClosestPointOnSegment(t,e,i))}return a&&(a.distance=Math.sqrt(o)),a},getBounds:function(){var t,e,i=new n.LatLngBounds,o=this.getLatLngs();for(t=0,e=o.length;e>t;t++)i.extend(o[t]);return i},_convertLatLngs:function(t){var e,i;for(e=0,i=t.length;i>e;e++){if(n.Util.isArray(t[e])&&"number"!=typeof t[e][0])return;t[e]=n.latLng(t[e])}return t},_initEvents:function(){n.Path.prototype._initEvents.call(this)},_getPathPartStr:function(t){for(var e,i=n.Path.VML,o=0,s=t.length,a="";s>o;o++)e=t[o],i&&e._round(),a+=(o?"L":"M")+e.x+" "+e.y;return a},_clipPoints:function(){var t,e,o,s=this._originalPoints,a=s.length;if(this.options.noClip)return this._parts=[s],i;this._parts=[];var r=this._parts,h=this._map._pathViewport,l=n.LineUtil;for(t=0,e=0;a-1>t;t++)o=l.clipSegment(s[t],s[t+1],h,t),o&&(r[e]=r[e]||[],r[e].push(o[0]),(o[1]!==s[t+1]||t===a-2)&&(r[e].push(o[1]),e++))},_simplifyPoints:function(){for(var t=this._parts,e=n.LineUtil,i=0,o=t.length;o>i;i++)t[i]=e.simplify(t[i],this.options.smoothFactor)},_updatePath:function(){this._map&&(this._clipPoints(),this._simplifyPoints(),n.Path.prototype._updatePath.call(this))}}),n.polyline=function(t,e){return new n.Polyline(t,e)},n.PolyUtil={},n.PolyUtil.clipPolygon=function(t,e){var i,o,s,a,r,h,l,u,c,_=[1,4,2,8],d=n.LineUtil;for(o=0,l=t.length;l>o;o++)t[o]._code=d._getBitCode(t[o],e);for(a=0;4>a;a++){for(u=_[a],i=[],o=0,l=t.length,s=l-1;l>o;s=o++)r=t[o],h=t[s],r._code&u?h._code&u||(c=d._getEdgeIntersection(h,r,u,e),c._code=d._getBitCode(c,e),i.push(c)):(h._code&u&&(c=d._getEdgeIntersection(h,r,u,e),c._code=d._getBitCode(c,e),i.push(c)),i.push(r));t=i}return t},n.Polygon=n.Polyline.extend({options:{fill:!0},initialize:function(t,e){n.Polyline.prototype.initialize.call(this,t,e),t&&n.Util.isArray(t[0])&&"number"!=typeof t[0][0]&&(this._latlngs=this._convertLatLngs(t[0]),this._holes=t.slice(1))},projectLatlngs:function(){if(n.Polyline.prototype.projectLatlngs.call(this),this._holePoints=[],this._holes){var t,e,i,o;for(t=0,i=this._holes.length;i>t;t++)for(this._holePoints[t]=[],e=0,o=this._holes[t].length;o>e;e++)this._holePoints[t][e]=this._map.latLngToLayerPoint(this._holes[t][e])}},_clipPoints:function(){var t=this._originalPoints,e=[];if(this._parts=[t].concat(this._holePoints),!this.options.noClip){for(var i=0,o=this._parts.length;o>i;i++){var s=n.PolyUtil.clipPolygon(this._parts[i],this._map._pathViewport);s.length&&e.push(s)}this._parts=e}},_getPathPartStr:function(t){var e=n.Polyline.prototype._getPathPartStr.call(this,t);return e+(n.Browser.svg?"z":"x")}}),n.polygon=function(t,e){return new n.Polygon(t,e)},function(){function t(t){return n.FeatureGroup.extend({initialize:function(t,e){this._layers={},this._options=e,this.setLatLngs(t)},setLatLngs:function(e){var i=0,n=e.length;for(this.eachLayer(function(t){n>i?t.setLatLngs(e[i++]):this.removeLayer(t)},this);n>i;)this.addLayer(new t(e[i++],this._options));return this}})}n.MultiPolyline=t(n.Polyline),n.MultiPolygon=t(n.Polygon),n.multiPolyline=function(t,e){return new n.MultiPolyline(t,e)},n.multiPolygon=function(t,e){return new n.MultiPolygon(t,e)}}(),n.Rectangle=n.Polygon.extend({initialize:function(t,e){n.Polygon.prototype.initialize.call(this,this._boundsToLatLngs(t),e)},setBounds:function(t){this.setLatLngs(this._boundsToLatLngs(t))},_boundsToLatLngs:function(t){return t=n.latLngBounds(t),[t.getSouthWest(),t.getNorthWest(),t.getNorthEast(),t.getSouthEast()]}}),n.rectangle=function(t,e){return new n.Rectangle(t,e)},n.Circle=n.Path.extend({initialize:function(t,e,i){n.Path.prototype.initialize.call(this,i),this._latlng=n.latLng(t),this._mRadius=e},options:{fill:!0},setLatLng:function(t){return this._latlng=n.latLng(t),this.redraw()},setRadius:function(t){return this._mRadius=t,this.redraw()},projectLatlngs:function(){var t=this._getLngRadius(),e=new n.LatLng(this._latlng.lat,this._latlng.lng-t),i=this._map.latLngToLayerPoint(e);this._point=this._map.latLngToLayerPoint(this._latlng),this._radius=Math.max(Math.round(this._point.x-i.x),1)},getBounds:function(){var t=this._getLngRadius(),e=360*(this._mRadius/40075017),i=this._latlng,o=new n.LatLng(i.lat-e,i.lng-t),s=new n.LatLng(i.lat+e,i.lng+t);return new n.LatLngBounds(o,s)},getLatLng:function(){return this._latlng},getPathString:function(){var t=this._point,e=this._radius;return this._checkIfEmpty()?"":n.Browser.svg?"M"+t.x+","+(t.y-e)+"A"+e+","+e+",0,1,1,"+(t.x-.1)+","+(t.y-e)+" z":(t._round(),e=Math.round(e),"AL "+t.x+","+t.y+" "+e+","+e+" 0,"+23592600)},getRadius:function(){return this._mRadius},_getLatRadius:function(){return 360*(this._mRadius/40075017)},_getLngRadius:function(){return this._getLatRadius()/Math.cos(n.LatLng.DEG_TO_RAD*this._latlng.lat)},_checkIfEmpty:function(){if(!this._map)return!1;var t=this._map._pathViewport,e=this._radius,i=this._point;return i.x-e>t.max.x||i.y-e>t.max.y||i.x+e<t.min.x||i.y+e<t.min.y}}),n.circle=function(t,e,i){return new n.Circle(t,e,i)},n.CircleMarker=n.Circle.extend({options:{radius:10,weight:2},initialize:function(t,e){n.Circle.prototype.initialize.call(this,t,null,e),this._radius=this.options.radius},projectLatlngs:function(){this._point=this._map.latLngToLayerPoint(this._latlng)},_updateStyle:function(){n.Circle.prototype._updateStyle.call(this),this.setRadius(this.options.radius)},setRadius:function(t){return this.options.radius=this._radius=t,this.redraw()}}),n.circleMarker=function(t,e){return new n.CircleMarker(t,e)},n.Polyline.include(n.Path.CANVAS?{_containsPoint:function(t,e){var i,o,s,a,r,h,l,u=this.options.weight/2;for(n.Browser.touch&&(u+=10),i=0,a=this._parts.length;a>i;i++)for(l=this._parts[i],o=0,r=l.length,s=r-1;r>o;s=o++)if((e||0!==o)&&(h=n.LineUtil.pointToSegmentDistance(t,l[s],l[o]),u>=h))return!0;return!1}}:{}),n.Polygon.include(n.Path.CANVAS?{_containsPoint:function(t){var e,i,o,s,a,r,h,l,u=!1;if(n.Polyline.prototype._containsPoint.call(this,t,!0))return!0;for(s=0,h=this._parts.length;h>s;s++)for(e=this._parts[s],a=0,l=e.length,r=l-1;l>a;r=a++)i=e[a],o=e[r],i.y>t.y!=o.y>t.y&&t.x<(o.x-i.x)*(t.y-i.y)/(o.y-i.y)+i.x&&(u=!u);return u}}:{}),n.Circle.include(n.Path.CANVAS?{_drawPath:function(){var t=this._point;this._ctx.beginPath(),this._ctx.arc(t.x,t.y,this._radius,0,2*Math.PI,!1)},_containsPoint:function(t){var e=this._point,i=this.options.stroke?this.options.weight/2:0;return t.distanceTo(e)<=this._radius+i}}:{}),n.GeoJSON=n.FeatureGroup.extend({initialize:function(t,e){n.setOptions(this,e),this._layers={},t&&this.addData(t)},addData:function(t){var e,i,o=n.Util.isArray(t)?t:t.features;if(o){for(e=0,i=o.length;i>e;e++)(o[e].geometries||o[e].geometry||o[e].features)&&this.addData(o[e]);return this}var s=this.options;if(!s.filter||s.filter(t)){var a=n.GeoJSON.geometryToLayer(t,s.pointToLayer);return a.feature=t,a.defaultOptions=a.options,this.resetStyle(a),s.onEachFeature&&s.onEachFeature(t,a),this.addLayer(a)}},resetStyle:function(t){var e=this.options.style;e&&(n.Util.extend(t.options,t.defaultOptions),this._setLayerStyle(t,e))},setStyle:function(t){this.eachLayer(function(e){this._setLayerStyle(e,t)},this)},_setLayerStyle:function(t,e){"function"==typeof e&&(e=e(t.feature)),t.setStyle&&t.setStyle(e)}}),n.extend(n.GeoJSON,{geometryToLayer:function(t,e){var i,o,s,a,r,h="Feature"===t.type?t.geometry:t,l=h.coordinates,u=[];switch(h.type){case"Point":return i=this.coordsToLatLng(l),e?e(t,i):new n.Marker(i);case"MultiPoint":for(s=0,a=l.length;a>s;s++)i=this.coordsToLatLng(l[s]),r=e?e(t,i):new n.Marker(i),u.push(r);return new n.FeatureGroup(u);case"LineString":return o=this.coordsToLatLngs(l),new n.Polyline(o);case"Polygon":return o=this.coordsToLatLngs(l,1),new n.Polygon(o);case"MultiLineString":return o=this.coordsToLatLngs(l,1),new n.MultiPolyline(o);case"MultiPolygon":return o=this.coordsToLatLngs(l,2),new n.MultiPolygon(o);case"GeometryCollection":for(s=0,a=h.geometries.length;a>s;s++)r=this.geometryToLayer({geometry:h.geometries[s],type:"Feature",properties:t.properties},e),u.push(r);return new n.FeatureGroup(u);default:throw Error("Invalid GeoJSON object.")}},coordsToLatLng:function(t,e){var i=parseFloat(t[e?0:1]),o=parseFloat(t[e?1:0]);return new n.LatLng(i,o)},coordsToLatLngs:function(t,e,i){var n,o,s,a=[];for(o=0,s=t.length;s>o;o++)n=e?this.coordsToLatLngs(t[o],e-1,i):this.coordsToLatLng(t[o],i),a.push(n);return a}}),n.geoJson=function(t,e){return new n.GeoJSON(t,e)},n.DomEvent={addListener:function(t,e,o,s){var a,r,h,l=n.stamp(o),u="_leaflet_"+e+l;return t[u]?this:(a=function(e){return o.call(s||t,e||n.DomEvent._getEvent())},n.Browser.msTouch&&0===e.indexOf("touch")?this.addMsTouchListener(t,e,a,l):(n.Browser.touch&&"dblclick"===e&&this.addDoubleTapListener&&this.addDoubleTapListener(t,a,l),"addEventListener"in t?"mousewheel"===e?(t.addEventListener("DOMMouseScroll",a,!1),t.addEventListener(e,a,!1)):"mouseenter"===e||"mouseleave"===e?(r=a,h="mouseenter"===e?"mouseover":"mouseout",a=function(e){return n.DomEvent._checkMouse(t,e)?r(e):i},t.addEventListener(h,a,!1)):t.addEventListener(e,a,!1):"attachEvent"in t&&t.attachEvent("on"+e,a),t[u]=a,this))},removeListener:function(t,e,i){var o=n.stamp(i),s="_leaflet_"+e+o,a=t[s];if(a)return n.Browser.msTouch&&0===e.indexOf("touch")?this.removeMsTouchListener(t,e,o):n.Browser.touch&&"dblclick"===e&&this.removeDoubleTapListener?this.removeDoubleTapListener(t,o):"removeEventListener"in t?"mousewheel"===e?(t.removeEventListener("DOMMouseScroll",a,!1),t.removeEventListener(e,a,!1)):"mouseenter"===e||"mouseleave"===e?t.removeEventListener("mouseenter"===e?"mouseover":"mouseout",a,!1):t.removeEventListener(e,a,!1):"detachEvent"in t&&t.detachEvent("on"+e,a),t[s]=null,this},stopPropagation:function(t){return t.stopPropagation?t.stopPropagation():t.cancelBubble=!0,this},disableClickPropagation:function(t){for(var e=n.DomEvent.stopPropagation,i=n.Draggable.START.length-1;i>=0;i--)n.DomEvent.addListener(t,n.Draggable.START[i],e);return n.DomEvent.addListener(t,"click",e).addListener(t,"dblclick",e)},preventDefault:function(t){return t.preventDefault?t.preventDefault():t.returnValue=!1,this},stop:function(t){return n.DomEvent.preventDefault(t).stopPropagation(t)},getMousePosition:function(t,i){var o=e.body,s=e.documentElement,a=t.pageX?t.pageX:t.clientX+o.scrollLeft+s.scrollLeft,r=t.pageY?t.pageY:t.clientY+o.scrollTop+s.scrollTop,h=new n.Point(a,r);return i?h._subtract(n.DomUtil.getViewportOffset(i)):h},getWheelDelta:function(t){var e=0;return t.wheelDelta&&(e=t.wheelDelta/120),t.detail&&(e=-t.detail/3),e},_checkMouse:function(t,e){var i=e.relatedTarget;if(!i)return!0;try{for(;i&&i!==t;)i=i.parentNode}catch(n){return!1}return i!==t},_getEvent:function(){var e=t.event;if(!e)for(var i=arguments.callee.caller;i&&(e=i.arguments[0],!e||t.Event!==e.constructor);)i=i.caller;return e}},n.DomEvent.on=n.DomEvent.addListener,n.DomEvent.off=n.DomEvent.removeListener,n.Draggable=n.Class.extend({includes:n.Mixin.Events,statics:{START:n.Browser.touch?["touchstart","mousedown"]:["mousedown"],END:{mousedown:"mouseup",touchstart:"touchend",MSPointerDown:"touchend"},MOVE:{mousedown:"mousemove",touchstart:"touchmove",MSPointerDown:"touchmove"},TAP_TOLERANCE:15},initialize:function(t,e,i){this._element=t,this._dragStartTarget=e||t,this._longPress=i&&!n.Browser.msTouch},enable:function(){if(!this._enabled){for(var t=n.Draggable.START.length-1;t>=0;t--)n.DomEvent.on(this._dragStartTarget,n.Draggable.START[t],this._onDown,this);this._enabled=!0}},disable:function(){if(this._enabled){for(var t=n.Draggable.START.length-1;t>=0;t--)n.DomEvent.off(this._dragStartTarget,n.Draggable.START[t],this._onDown,this);this._enabled=!1,this._moved=!1}},_onDown:function(t){if(!(!n.Browser.touch&&t.shiftKey||1!==t.which&&1!==t.button&&!t.touches||(n.DomEvent.preventDefault(t),n.DomEvent.stopPropagation(t),n.Draggable._disabled))){if(this._simulateClick=!0,t.touches&&t.touches.length>1)return this._simulateClick=!1,clearTimeout(this._longPressTimeout),i;var o=t.touches&&1===t.touches.length?t.touches[0]:t,s=o.target;n.Browser.touch&&"a"===s.tagName.toLowerCase()&&n.DomUtil.addClass(s,"leaflet-active"),this._moved=!1,this._moving||(this._startPoint=new n.Point(o.clientX,o.clientY),this._startPos=this._newPos=n.DomUtil.getPosition(this._element),t.touches&&1===t.touches.length&&n.Browser.touch&&this._longPress&&(this._longPressTimeout=setTimeout(n.bind(function(){var t=this._newPos&&this._newPos.distanceTo(this._startPos)||0;n.Draggable.TAP_TOLERANCE>t&&(this._simulateClick=!1,this._onUp(),this._simulateEvent("contextmenu",o))},this),1e3)),n.DomEvent.on(e,n.Draggable.MOVE[t.type],this._onMove,this),n.DomEvent.on(e,n.Draggable.END[t.type],this._onUp,this))}},_onMove:function(t){if(!(t.touches&&t.touches.length>1)){var e=t.touches&&1===t.touches.length?t.touches[0]:t,i=new n.Point(e.clientX,e.clientY),o=i.subtract(this._startPoint);(o.x||o.y)&&(n.DomEvent.preventDefault(t),this._moved||(this.fire("dragstart"),this._moved=!0,this._startPos=n.DomUtil.getPosition(this._element).subtract(o),n.Browser.touch||(n.DomUtil.disableTextSelection(),this._setMovingCursor())),this._newPos=this._startPos.add(o),this._moving=!0,n.Util.cancelAnimFrame(this._animRequest),this._animRequest=n.Util.requestAnimFrame(this._updatePosition,this,!0,this._dragStartTarget))}},_updatePosition:function(){this.fire("predrag"),n.DomUtil.setPosition(this._element,this._newPos),this.fire("drag")},_onUp:function(t){var i;if(clearTimeout(this._longPressTimeout),this._simulateClick&&t.changedTouches){var o=t.changedTouches[0],s=o.target,a=this._newPos&&this._newPos.distanceTo(this._startPos)||0;"a"===s.tagName.toLowerCase()&&n.DomUtil.removeClass(s,"leaflet-active"),n.Draggable.TAP_TOLERANCE>a&&(i=o)}n.Browser.touch||(n.DomUtil.enableTextSelection(),this._restoreCursor());for(var r in n.Draggable.MOVE)n.Draggable.MOVE.hasOwnProperty(r)&&(n.DomEvent.off(e,n.Draggable.MOVE[r],this._onMove),n.DomEvent.off(e,n.Draggable.END[r],this._onUp));this._moved&&(n.Util.cancelAnimFrame(this._animRequest),this.fire("dragend")),this._moving=!1,i&&(this._moved=!1,this._simulateEvent("click",i))},_setMovingCursor:function(){n.DomUtil.addClass(e.body,"leaflet-dragging")},_restoreCursor:function(){n.DomUtil.removeClass(e.body,"leaflet-dragging")},_simulateEvent:function(i,n){var o=e.createEvent("MouseEvents");o.initMouseEvent(i,!0,!0,t,1,n.screenX,n.screenY,n.clientX,n.clientY,!1,!1,!1,!1,0,null),n.target.dispatchEvent(o)}}),n.Handler=n.Class.extend({initialize:function(t){this._map=t},enable:function(){this._enabled||(this._enabled=!0,this.addHooks())},disable:function(){this._enabled&&(this._enabled=!1,this.removeHooks())},enabled:function(){return!!this._enabled}}),n.Map.mergeOptions({dragging:!0,inertia:!n.Browser.android23,inertiaDeceleration:3400,inertiaMaxSpeed:1/0,inertiaThreshold:n.Browser.touch?32:18,easeLinearity:.25,longPress:!0,worldCopyJump:!1}),n.Map.Drag=n.Handler.extend({addHooks:function(){if(!this._draggable){var t=this._map;this._draggable=new n.Draggable(t._mapPane,t._container,t.options.longPress),this._draggable.on({dragstart:this._onDragStart,drag:this._onDrag,dragend:this._onDragEnd},this),t.options.worldCopyJump&&(this._draggable.on("predrag",this._onPreDrag,this),t.on("viewreset",this._onViewReset,this))}this._draggable.enable()},removeHooks:function(){this._draggable.disable()},moved:function(){return this._draggable&&this._draggable._moved},_onDragStart:function(){var t=this._map;t._panAnim&&t._panAnim.stop(),t.fire("movestart").fire("dragstart"),t.options.inertia&&(this._positions=[],this._times=[])},_onDrag:function(){if(this._map.options.inertia){var t=this._lastTime=+new Date,e=this._lastPos=this._draggable._newPos;this._positions.push(e),this._times.push(t),t-this._times[0]>200&&(this._positions.shift(),this._times.shift())}this._map.fire("move").fire("drag")},_onViewReset:function(){var t=this._map.getSize()._divideBy(2),e=this._map.latLngToLayerPoint(new n.LatLng(0,0));this._initialWorldOffset=e.subtract(t).x,this._worldWidth=this._map.project(new n.LatLng(0,180)).x},_onPreDrag:function(){var t=this._worldWidth,e=Math.round(t/2),i=this._initialWorldOffset,n=this._draggable._newPos.x,o=(n-e+i)%t+e-i,s=(n+e+i)%t-e-i,a=Math.abs(o+i)<Math.abs(s+i)?o:s;this._draggable._newPos.x=a},_onDragEnd:function(){var t=this._map,e=t.options,i=+new Date-this._lastTime,o=!e.inertia||i>e.inertiaThreshold||!this._positions[0];if(o)t.fire("moveend");else{var s=this._lastPos.subtract(this._positions[0]),a=(this._lastTime+i-this._times[0])/1e3,r=e.easeLinearity,h=s.multiplyBy(r/a),l=h.distanceTo(new n.Point(0,0)),u=Math.min(e.inertiaMaxSpeed,l),c=h.multiplyBy(u/l),_=u/(e.inertiaDeceleration*r),d=c.multiplyBy(-_/2).round();n.Util.requestAnimFrame(function(){t.panBy(d,_,r)})}t.fire("dragend"),e.maxBounds&&n.Util.requestAnimFrame(this._panInsideMaxBounds,t,!0,t._container)},_panInsideMaxBounds:function(){this.panInsideBounds(this.options.maxBounds)}}),n.Map.addInitHook("addHandler","dragging",n.Map.Drag),n.Map.mergeOptions({doubleClickZoom:!0}),n.Map.DoubleClickZoom=n.Handler.extend({addHooks:function(){this._map.on("dblclick",this._onDoubleClick)},removeHooks:function(){this._map.off("dblclick",this._onDoubleClick)},_onDoubleClick:function(t){this.setView(t.latlng,this._zoom+1)}}),n.Map.addInitHook("addHandler","doubleClickZoom",n.Map.DoubleClickZoom),n.Map.mergeOptions({scrollWheelZoom:!0}),n.Map.ScrollWheelZoom=n.Handler.extend({addHooks:function(){n.DomEvent.on(this._map._container,"mousewheel",this._onWheelScroll,this),this._delta=0},removeHooks:function(){n.DomEvent.off(this._map._container,"mousewheel",this._onWheelScroll)},_onWheelScroll:function(t){var e=n.DomEvent.getWheelDelta(t);this._delta+=e,this._lastMousePos=this._map.mouseEventToContainerPoint(t),this._startTime||(this._startTime=+new Date);var i=Math.max(40-(+new Date-this._startTime),0);clearTimeout(this._timer),this._timer=setTimeout(n.bind(this._performZoom,this),i),n.DomEvent.preventDefault(t),n.DomEvent.stopPropagation(t)},_performZoom:function(){var t=this._map,e=this._delta,i=t.getZoom();if(e=e>0?Math.ceil(e):Math.round(e),e=Math.max(Math.min(e,4),-4),e=t._limitZoom(i+e)-i,this._delta=0,this._startTime=null,e){var n=i+e,o=this._getCenterForScrollWheelZoom(n);t.setView(o,n)}},_getCenterForScrollWheelZoom:function(t){var e=this._map,i=e.getZoomScale(t),n=e.getSize()._divideBy(2),o=this._lastMousePos._subtract(n)._multiplyBy(1-1/i),s=e._getTopLeftPoint()._add(n)._add(o);return e.unproject(s)}}),n.Map.addInitHook("addHandler","scrollWheelZoom",n.Map.ScrollWheelZoom),n.extend(n.DomEvent,{_touchstart:n.Browser.msTouch?"MSPointerDown":"touchstart",_touchend:n.Browser.msTouch?"MSPointerUp":"touchend",addDoubleTapListener:function(t,i,o){function s(t){var e;if(n.Browser.msTouch?(p.push(t.pointerId),e=p.length):e=t.touches.length,!(e>1)){var i=Date.now(),o=i-(r||i);h=t.touches?t.touches[0]:t,l=o>0&&u>=o,r=i}}function a(t){if(n.Browser.msTouch){var e=p.indexOf(t.pointerId);if(-1===e)return;p.splice(e,1)}if(l){if(n.Browser.msTouch){var o,s={};for(var a in h)o=h[a],s[a]="function"==typeof o?o.bind(h):o;h=s}h.type="dblclick",i(h),r=null}}var r,h,l=!1,u=250,c="_leaflet_",_=this._touchstart,d=this._touchend,p=[];t[c+_+o]=s,t[c+d+o]=a;var m=n.Browser.msTouch?e.documentElement:t;return t.addEventListener(_,s,!1),m.addEventListener(d,a,!1),n.Browser.msTouch&&m.addEventListener("MSPointerCancel",a,!1),this},removeDoubleTapListener:function(t,i){var o="_leaflet_";return t.removeEventListener(this._touchstart,t[o+this._touchstart+i],!1),(n.Browser.msTouch?e.documentElement:t).removeEventListener(this._touchend,t[o+this._touchend+i],!1),n.Browser.msTouch&&e.documentElement.removeEventListener("MSPointerCancel",t[o+this._touchend+i],!1),this}}),n.extend(n.DomEvent,{_msTouches:[],_msDocumentListener:!1,addMsTouchListener:function(t,e,i,n){switch(e){case"touchstart":return this.addMsTouchListenerStart(t,e,i,n);case"touchend":return this.addMsTouchListenerEnd(t,e,i,n);case"touchmove":return this.addMsTouchListenerMove(t,e,i,n);default:throw"Unknown touch event type"}},addMsTouchListenerStart:function(t,i,n,o){var s="_leaflet_",a=this._msTouches,r=function(t){for(var e=!1,i=0;a.length>i;i++)if(a[i].pointerId===t.pointerId){e=!0;break}e||a.push(t),t.touches=a.slice(),t.changedTouches=[t],n(t)};if(t[s+"touchstart"+o]=r,t.addEventListener("MSPointerDown",r,!1),!this._msDocumentListener){var h=function(t){for(var e=0;a.length>e;e++)if(a[e].pointerId===t.pointerId){a.splice(e,1);break}};e.documentElement.addEventListener("MSPointerUp",h,!1),e.documentElement.addEventListener("MSPointerCancel",h,!1),this._msDocumentListener=!0}return this},addMsTouchListenerMove:function(t,e,i,n){function o(t){if(t.pointerType!==t.MSPOINTER_TYPE_MOUSE||0!==t.buttons){for(var e=0;a.length>e;e++)if(a[e].pointerId===t.pointerId){a[e]=t;break}t.touches=a.slice(),t.changedTouches=[t],i(t)}}var s="_leaflet_",a=this._msTouches;return t[s+"touchmove"+n]=o,t.addEventListener("MSPointerMove",o,!1),this},addMsTouchListenerEnd:function(t,e,i,n){var o="_leaflet_",s=this._msTouches,a=function(t){for(var e=0;s.length>e;e++)if(s[e].pointerId===t.pointerId){s.splice(e,1);break}t.touches=s.slice(),t.changedTouches=[t],i(t)};return t[o+"touchend"+n]=a,t.addEventListener("MSPointerUp",a,!1),t.addEventListener("MSPointerCancel",a,!1),this},removeMsTouchListener:function(t,e,i){var n="_leaflet_",o=t[n+e+i];switch(e){case"touchstart":t.removeEventListener("MSPointerDown",o,!1);break;case"touchmove":t.removeEventListener("MSPointerMove",o,!1);break;case"touchend":t.removeEventListener("MSPointerUp",o,!1),t.removeEventListener("MSPointerCancel",o,!1)}return this}}),n.Map.mergeOptions({touchZoom:n.Browser.touch&&!n.Browser.android23}),n.Map.TouchZoom=n.Handler.extend({addHooks:function(){n.DomEvent.on(this._map._container,"touchstart",this._onTouchStart,this)},removeHooks:function(){n.DomEvent.off(this._map._container,"touchstart",this._onTouchStart,this)},_onTouchStart:function(t){var i=this._map;if(t.touches&&2===t.touches.length&&!i._animatingZoom&&!this._zooming){var o=i.mouseEventToLayerPoint(t.touches[0]),s=i.mouseEventToLayerPoint(t.touches[1]),a=i._getCenterLayerPoint();this._startCenter=o.add(s)._divideBy(2),this._startDist=o.distanceTo(s),this._moved=!1,this._zooming=!0,this._centerOffset=a.subtract(this._startCenter),i._panAnim&&i._panAnim.stop(),n.DomEvent.on(e,"touchmove",this._onTouchMove,this).on(e,"touchend",this._onTouchEnd,this),n.DomEvent.preventDefault(t)}},_onTouchMove:function(t){if(t.touches&&2===t.touches.length){var e=this._map,i=e.mouseEventToLayerPoint(t.touches[0]),o=e.mouseEventToLayerPoint(t.touches[1]);this._scale=i.distanceTo(o)/this._startDist,this._delta=i._add(o)._divideBy(2)._subtract(this._startCenter),1!==this._scale&&(this._moved||(n.DomUtil.addClass(e._mapPane,"leaflet-zoom-anim leaflet-touching"),e.fire("movestart").fire("zoomstart")._prepareTileBg(),this._moved=!0),n.Util.cancelAnimFrame(this._animRequest),this._animRequest=n.Util.requestAnimFrame(this._updateOnMove,this,!0,this._map._container),n.DomEvent.preventDefault(t))}},_updateOnMove:function(){var t=this._map,e=this._getScaleOrigin(),i=t.layerPointToLatLng(e);t.fire("zoomanim",{center:i,zoom:t.getScaleZoom(this._scale)}),t._tileBg.style[n.DomUtil.TRANSFORM]=n.DomUtil.getTranslateString(this._delta)+" "+n.DomUtil.getScaleString(this._scale,this._startCenter)},_onTouchEnd:function(){if(this._moved&&this._zooming){var t=this._map;this._zooming=!1,n.DomUtil.removeClass(t._mapPane,"leaflet-touching"),n.DomEvent.off(e,"touchmove",this._onTouchMove).off(e,"touchend",this._onTouchEnd);var i=this._getScaleOrigin(),o=t.layerPointToLatLng(i),s=t.getZoom(),a=t.getScaleZoom(this._scale)-s,r=a>0?Math.ceil(a):Math.floor(a),h=t._limitZoom(s+r);t.fire("zoomanim",{center:o,zoom:h}),t._runAnimation(o,h,t.getZoomScale(h)/this._scale,i,!0)}},_getScaleOrigin:function(){var t=this._centerOffset.subtract(this._delta).divideBy(this._scale);return this._startCenter.add(t)}}),n.Map.addInitHook("addHandler","touchZoom",n.Map.TouchZoom),n.Map.mergeOptions({boxZoom:!0}),n.Map.BoxZoom=n.Handler.extend({initialize:function(t){this._map=t,this._container=t._container,this._pane=t._panes.overlayPane},addHooks:function(){n.DomEvent.on(this._container,"mousedown",this._onMouseDown,this)},removeHooks:function(){n.DomEvent.off(this._container,"mousedown",this._onMouseDown)},_onMouseDown:function(t){return!t.shiftKey||1!==t.which&&1!==t.button?!1:(n.DomUtil.disableTextSelection(),this._startLayerPoint=this._map.mouseEventToLayerPoint(t),this._box=n.DomUtil.create("div","leaflet-zoom-box",this._pane),n.DomUtil.setPosition(this._box,this._startLayerPoint),this._container.style.cursor="crosshair",n.DomEvent.on(e,"mousemove",this._onMouseMove,this).on(e,"mouseup",this._onMouseUp,this).preventDefault(t),this._map.fire("boxzoomstart"),i)},_onMouseMove:function(t){var e=this._startLayerPoint,i=this._box,o=this._map.mouseEventToLayerPoint(t),s=o.subtract(e),a=new n.Point(Math.min(o.x,e.x),Math.min(o.y,e.y));n.DomUtil.setPosition(i,a),i.style.width=Math.max(0,Math.abs(s.x)-4)+"px",i.style.height=Math.max(0,Math.abs(s.y)-4)+"px"},_onMouseUp:function(t){this._pane.removeChild(this._box),this._container.style.cursor="",n.DomUtil.enableTextSelection(),n.DomEvent.off(e,"mousemove",this._onMouseMove).off(e,"mouseup",this._onMouseUp);var i=this._map,o=i.mouseEventToLayerPoint(t);if(!this._startLayerPoint.equals(o)){var s=new n.LatLngBounds(i.layerPointToLatLng(this._startLayerPoint),i.layerPointToLatLng(o));i.fitBounds(s),i.fire("boxzoomend",{boxZoomBounds:s})}}}),n.Map.addInitHook("addHandler","boxZoom",n.Map.BoxZoom),n.Map.mergeOptions({keyboard:!0,keyboardPanOffset:80,keyboardZoomOffset:1}),n.Map.Keyboard=n.Handler.extend({keyCodes:{left:[37],right:[39],down:[40],up:[38],zoomIn:[187,107,61],zoomOut:[189,109,173]},initialize:function(t){this._map=t,this._setPanOffset(t.options.keyboardPanOffset),this._setZoomOffset(t.options.keyboardZoomOffset)},addHooks:function(){var t=this._map._container;-1===t.tabIndex&&(t.tabIndex="0"),n.DomEvent.on(t,"focus",this._onFocus,this).on(t,"blur",this._onBlur,this).on(t,"mousedown",this._onMouseDown,this),this._map.on("focus",this._addHooks,this).on("blur",this._removeHooks,this)},removeHooks:function(){this._removeHooks();var t=this._map._container;n.DomEvent.off(t,"focus",this._onFocus,this).off(t,"blur",this._onBlur,this).off(t,"mousedown",this._onMouseDown,this),this._map.off("focus",this._addHooks,this).off("blur",this._removeHooks,this)},_onMouseDown:function(){this._focused||this._map._container.focus()},_onFocus:function(){this._focused=!0,this._map.fire("focus")},_onBlur:function(){this._focused=!1,this._map.fire("blur")},_setPanOffset:function(t){var e,i,n=this._panKeys={},o=this.keyCodes;for(e=0,i=o.left.length;i>e;e++)n[o.left[e]]=[-1*t,0];for(e=0,i=o.right.length;i>e;e++)n[o.right[e]]=[t,0];for(e=0,i=o.down.length;i>e;e++)n[o.down[e]]=[0,t];for(e=0,i=o.up.length;i>e;e++)n[o.up[e]]=[0,-1*t]},_setZoomOffset:function(t){var e,i,n=this._zoomKeys={},o=this.keyCodes;for(e=0,i=o.zoomIn.length;i>e;e++)n[o.zoomIn[e]]=t;for(e=0,i=o.zoomOut.length;i>e;e++)n[o.zoomOut[e]]=-t},_addHooks:function(){n.DomEvent.on(e,"keydown",this._onKeyDown,this)},_removeHooks:function(){n.DomEvent.off(e,"keydown",this._onKeyDown,this)},_onKeyDown:function(t){var e=t.keyCode,i=this._map;if(this._panKeys.hasOwnProperty(e))i.panBy(this._panKeys[e]),i.options.maxBounds&&i.panInsideBounds(i.options.maxBounds);else{if(!this._zoomKeys.hasOwnProperty(e))return;i.setZoom(i.getZoom()+this._zoomKeys[e])}n.DomEvent.stop(t)}}),n.Map.addInitHook("addHandler","keyboard",n.Map.Keyboard),n.Handler.MarkerDrag=n.Handler.extend({initialize:function(t){this._marker=t},addHooks:function(){var t=this._marker._icon;this._draggable||(this._draggable=new n.Draggable(t,t).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(){this._marker.closePopup().fire("movestart").fire("dragstart")},_onDrag:function(){var t=this._marker,e=t._shadow,i=n.DomUtil.getPosition(t._icon),o=t._map.layerPointToLatLng(i);e&&n.DomUtil.setPosition(e,i),t._latlng=o,t.fire("move",{latlng:o}).fire("drag")},_onDragEnd:function(){this._marker.fire("moveend").fire("dragend")}}),n.Handler.PolyEdit=n.Handler.extend({options:{icon:new n.DivIcon({iconSize:new n.Point(8,8),className:"leaflet-div-icon leaflet-editing-icon"})},initialize:function(t,e){this._poly=t,n.setOptions(this,e)},addHooks:function(){this._poly._map&&(this._markerGroup||this._initMarkers(),this._poly._map.addLayer(this._markerGroup))},removeHooks:function(){this._poly._map&&(this._poly._map.removeLayer(this._markerGroup),delete this._markerGroup,delete this._markers)},updateMarkers:function(){this._markerGroup.clearLayers(),this._initMarkers()},_initMarkers:function(){this._markerGroup||(this._markerGroup=new n.LayerGroup),this._markers=[];var t,e,i,o,s=this._poly._latlngs;for(t=0,i=s.length;i>t;t++)o=this._createMarker(s[t],t),o.on("click",this._onMarkerClick,this),this._markers.push(o);var a,r;for(t=0,e=i-1;i>t;e=t++)(0!==t||n.Polygon&&this._poly instanceof n.Polygon)&&(a=this._markers[e],r=this._markers[t],this._createMiddleMarker(a,r),this._updatePrevNext(a,r))},_createMarker:function(t,e){var i=new n.Marker(t,{draggable:!0,icon:this.options.icon});return i._origLatLng=t,i._index=e,i.on("drag",this._onMarkerDrag,this),i.on("dragend",this._fireEdit,this),this._markerGroup.addLayer(i),i},_fireEdit:function(){this._poly.fire("edit")},_onMarkerDrag:function(t){var e=t.target;n.extend(e._origLatLng,e._latlng),e._middleLeft&&e._middleLeft.setLatLng(this._getMiddleLatLng(e._prev,e)),e._middleRight&&e._middleRight.setLatLng(this._getMiddleLatLng(e,e._next)),this._poly.redraw()},_onMarkerClick:function(t){if(!(3>this._poly._latlngs.length)){var e=t.target,i=e._index;this._markerGroup.removeLayer(e),this._markers.splice(i,1),this._poly.spliceLatLngs(i,1),this._updateIndexes(i,-1),this._updatePrevNext(e._prev,e._next),e._middleLeft&&this._markerGroup.removeLayer(e._middleLeft),e._middleRight&&this._markerGroup.removeLayer(e._middleRight),e._prev&&e._next?this._createMiddleMarker(e._prev,e._next):e._prev?e._next||(e._prev._middleRight=null):e._next._middleLeft=null,this._poly.fire("edit")}},_updateIndexes:function(t,e){this._markerGroup.eachLayer(function(i){i._index>t&&(i._index+=e)})},_createMiddleMarker:function(t,e){var i,n,o,s=this._getMiddleLatLng(t,e),a=this._createMarker(s);a.setOpacity(.6),t._middleRight=e._middleLeft=a,n=function(){var n=e._index;a._index=n,a.off("click",i).on("click",this._onMarkerClick,this),s.lat=a.getLatLng().lat,s.lng=a.getLatLng().lng,this._poly.spliceLatLngs(n,0,s),this._markers.splice(n,0,a),a.setOpacity(1),this._updateIndexes(n,1),e._index++,this._updatePrevNext(t,a),this._updatePrevNext(a,e)},o=function(){a.off("dragstart",n,this),a.off("dragend",o,this),this._createMiddleMarker(t,a),this._createMiddleMarker(a,e)},i=function(){n.call(this),o.call(this),this._poly.fire("edit")},a.on("click",i,this).on("dragstart",n,this).on("dragend",o,this),this._markerGroup.addLayer(a)},_updatePrevNext:function(t,e){t&&(t._next=e),e&&(e._prev=t)},_getMiddleLatLng:function(t,e){var i=this._poly._map,n=i.latLngToLayerPoint(t.getLatLng()),o=i.latLngToLayerPoint(e.getLatLng());return i.layerPointToLatLng(n._add(o)._divideBy(2))}}),n.Polyline.addInitHook(function(){n.Handler.PolyEdit&&(this.editing=new n.Handler.PolyEdit(this),this.options.editable&&this.editing.enable()),this.on("add",function(){this.editing&&this.editing.enabled()&&this.editing.addHooks()}),this.on("remove",function(){this.editing&&this.editing.enabled()&&this.editing.removeHooks()})}),n.Control=n.Class.extend({options:{position:"topright"},initialize:function(t){n.setOptions(this,t)},getPosition:function(){return this.options.position},setPosition:function(t){var e=this._map;return e&&e.removeControl(this),this.options.position=t,e&&e.addControl(this),this
+},addTo:function(t){this._map=t;var e=this._container=this.onAdd(t),i=this.getPosition(),o=t._controlCorners[i];return n.DomUtil.addClass(e,"leaflet-control"),-1!==i.indexOf("bottom")?o.insertBefore(e,o.firstChild):o.appendChild(e),this},removeFrom:function(t){var e=this.getPosition(),i=t._controlCorners[e];return i.removeChild(this._container),this._map=null,this.onRemove&&this.onRemove(t),this}}),n.control=function(t){return new n.Control(t)},n.Map.include({addControl:function(t){return t.addTo(this),this},removeControl:function(t){return t.removeFrom(this),this},_initControlPos:function(){function t(t,s){var a=i+t+" "+i+s;e[t+s]=n.DomUtil.create("div",a,o)}var e=this._controlCorners={},i="leaflet-",o=this._controlContainer=n.DomUtil.create("div",i+"control-container",this._container);t("top","left"),t("top","right"),t("bottom","left"),t("bottom","right")}}),n.Control.Zoom=n.Control.extend({options:{position:"topleft"},onAdd:function(t){var e="leaflet-control-zoom",i="leaflet-bar",o=i+"-part",s=n.DomUtil.create("div",e+" "+i);return this._map=t,this._zoomInButton=this._createButton("+","Zoom in",e+"-in "+o+" "+o+"-top",s,this._zoomIn,this),this._zoomOutButton=this._createButton("-","Zoom out",e+"-out "+o+" "+o+"-bottom",s,this._zoomOut,this),t.on("zoomend",this._updateDisabled,this),s},onRemove:function(t){t.off("zoomend",this._updateDisabled,this)},_zoomIn:function(t){this._map.zoomIn(t.shiftKey?3:1)},_zoomOut:function(t){this._map.zoomOut(t.shiftKey?3:1)},_createButton:function(t,e,i,o,s,a){var r=n.DomUtil.create("a",i,o);r.innerHTML=t,r.href="#",r.title=e;var h=n.DomEvent.stopPropagation;return n.DomEvent.on(r,"click",h).on(r,"mousedown",h).on(r,"dblclick",h).on(r,"click",n.DomEvent.preventDefault).on(r,"click",s,a),r},_updateDisabled:function(){var t=this._map,e="leaflet-control-zoom-disabled";n.DomUtil.removeClass(this._zoomInButton,e),n.DomUtil.removeClass(this._zoomOutButton,e),t._zoom===t.getMinZoom()&&n.DomUtil.addClass(this._zoomOutButton,e),t._zoom===t.getMaxZoom()&&n.DomUtil.addClass(this._zoomInButton,e)}}),n.Map.mergeOptions({zoomControl:!0}),n.Map.addInitHook(function(){this.options.zoomControl&&(this.zoomControl=new n.Control.Zoom,this.addControl(this.zoomControl))}),n.control.zoom=function(t){return new n.Control.Zoom(t)},n.Control.Attribution=n.Control.extend({options:{position:"bottomright",prefix:'Powered by <a href="http://leafletjs.com">Leaflet</a>'},initialize:function(t){n.setOptions(this,t),this._attributions={}},onAdd:function(t){return this._container=n.DomUtil.create("div","leaflet-control-attribution"),n.DomEvent.disableClickPropagation(this._container),t.on("layeradd",this._onLayerAdd,this).on("layerremove",this._onLayerRemove,this),this._update(),this._container},onRemove:function(t){t.off("layeradd",this._onLayerAdd).off("layerremove",this._onLayerRemove)},setPrefix:function(t){return this.options.prefix=t,this._update(),this},addAttribution:function(t){return t?(this._attributions[t]||(this._attributions[t]=0),this._attributions[t]++,this._update(),this):i},removeAttribution:function(t){return t?(this._attributions[t]--,this._update(),this):i},_update:function(){if(this._map){var t=[];for(var e in this._attributions)this._attributions.hasOwnProperty(e)&&this._attributions[e]&&t.push(e);var i=[];this.options.prefix&&i.push(this.options.prefix),t.length&&i.push(t.join(", ")),this._container.innerHTML=i.join(" &#8212; ")}},_onLayerAdd:function(t){t.layer.getAttribution&&this.addAttribution(t.layer.getAttribution())},_onLayerRemove:function(t){t.layer.getAttribution&&this.removeAttribution(t.layer.getAttribution())}}),n.Map.mergeOptions({attributionControl:!0}),n.Map.addInitHook(function(){this.options.attributionControl&&(this.attributionControl=(new n.Control.Attribution).addTo(this))}),n.control.attribution=function(t){return new n.Control.Attribution(t)},n.Control.Scale=n.Control.extend({options:{position:"bottomleft",maxWidth:100,metric:!0,imperial:!0,updateWhenIdle:!1},onAdd:function(t){this._map=t;var e="leaflet-control-scale",i=n.DomUtil.create("div",e),o=this.options;return this._addScales(o,e,i),t.on(o.updateWhenIdle?"moveend":"move",this._update,this),t.whenReady(this._update,this),i},onRemove:function(t){t.off(this.options.updateWhenIdle?"moveend":"move",this._update,this)},_addScales:function(t,e,i){t.metric&&(this._mScale=n.DomUtil.create("div",e+"-line",i)),t.imperial&&(this._iScale=n.DomUtil.create("div",e+"-line",i))},_update:function(){var t=this._map.getBounds(),e=t.getCenter().lat,i=6378137*Math.PI*Math.cos(e*Math.PI/180),n=i*(t.getNorthEast().lng-t.getSouthWest().lng)/180,o=this._map.getSize(),s=this.options,a=0;o.x>0&&(a=n*(s.maxWidth/o.x)),this._updateScales(s,a)},_updateScales:function(t,e){t.metric&&e&&this._updateMetric(e),t.imperial&&e&&this._updateImperial(e)},_updateMetric:function(t){var e=this._getRoundNum(t);this._mScale.style.width=this._getScaleWidth(e/t)+"px",this._mScale.innerHTML=1e3>e?e+" m":e/1e3+" km"},_updateImperial:function(t){var e,i,n,o=3.2808399*t,s=this._iScale;o>5280?(e=o/5280,i=this._getRoundNum(e),s.style.width=this._getScaleWidth(i/e)+"px",s.innerHTML=i+" mi"):(n=this._getRoundNum(o),s.style.width=this._getScaleWidth(n/o)+"px",s.innerHTML=n+" ft")},_getScaleWidth:function(t){return Math.round(this.options.maxWidth*t)-10},_getRoundNum:function(t){var e=Math.pow(10,(Math.floor(t)+"").length-1),i=t/e;return i=i>=10?10:i>=5?5:i>=3?3:i>=2?2:1,e*i}}),n.control.scale=function(t){return new n.Control.Scale(t)},n.Control.Layers=n.Control.extend({options:{collapsed:!0,position:"topright",autoZIndex:!0},initialize:function(t,e,i){n.setOptions(this,i),this._layers={},this._lastZIndex=0,this._handlingClick=!1;for(var o in t)t.hasOwnProperty(o)&&this._addLayer(t[o],o);for(o in e)e.hasOwnProperty(o)&&this._addLayer(e[o],o,!0)},onAdd:function(t){return this._initLayout(),this._update(),t.on("layeradd",this._onLayerChange,this).on("layerremove",this._onLayerChange,this),this._container},onRemove:function(t){t.off("layeradd",this._onLayerChange).off("layerremove",this._onLayerChange)},addBaseLayer:function(t,e){return this._addLayer(t,e),this._update(),this},addOverlay:function(t,e){return this._addLayer(t,e,!0),this._update(),this},removeLayer:function(t){var e=n.stamp(t);return delete this._layers[e],this._update(),this},_initLayout:function(){var t="leaflet-control-layers",e=this._container=n.DomUtil.create("div",t);n.Browser.touch?n.DomEvent.on(e,"click",n.DomEvent.stopPropagation):(n.DomEvent.disableClickPropagation(e),n.DomEvent.on(e,"mousewheel",n.DomEvent.stopPropagation));var i=this._form=n.DomUtil.create("form",t+"-list");if(this.options.collapsed){n.DomEvent.on(e,"mouseover",this._expand,this).on(e,"mouseout",this._collapse,this);var o=this._layersLink=n.DomUtil.create("a",t+"-toggle",e);o.href="#",o.title="Layers",n.Browser.touch?n.DomEvent.on(o,"click",n.DomEvent.stopPropagation).on(o,"click",n.DomEvent.preventDefault).on(o,"click",this._expand,this):n.DomEvent.on(o,"focus",this._expand,this),this._map.on("movestart",this._collapse,this)}else this._expand();this._baseLayersList=n.DomUtil.create("div",t+"-base",i),this._separator=n.DomUtil.create("div",t+"-separator",i),this._overlaysList=n.DomUtil.create("div",t+"-overlays",i),e.appendChild(i)},_addLayer:function(t,e,i){var o=n.stamp(t);this._layers[o]={layer:t,name:e,overlay:i},this.options.autoZIndex&&t.setZIndex&&(this._lastZIndex++,t.setZIndex(this._lastZIndex))},_update:function(){if(this._container){this._baseLayersList.innerHTML="",this._overlaysList.innerHTML="";var t=!1,e=!1;for(var i in this._layers)if(this._layers.hasOwnProperty(i)){var n=this._layers[i];this._addItem(n),e=e||n.overlay,t=t||!n.overlay}this._separator.style.display=e&&t?"":"none"}},_onLayerChange:function(t){var e=n.stamp(t.layer);this._layers[e]&&!this._handlingClick&&this._update()},_createRadioElement:function(t,i){var n='<input type="radio" class="leaflet-control-layers-selector" name="'+t+'"';i&&(n+=' checked="checked"'),n+="/>";var o=e.createElement("div");return o.innerHTML=n,o.firstChild},_addItem:function(t){var i,o=e.createElement("label"),s=this._map.hasLayer(t.layer);t.overlay?(i=e.createElement("input"),i.type="checkbox",i.className="leaflet-control-layers-selector",i.defaultChecked=s):i=this._createRadioElement("leaflet-base-layers",s),i.layerId=n.stamp(t.layer),n.DomEvent.on(i,"click",this._onInputClick,this);var a=e.createElement("span");a.innerHTML=" "+t.name,o.appendChild(i),o.appendChild(a);var r=t.overlay?this._overlaysList:this._baseLayersList;return r.appendChild(o),o},_onInputClick:function(){var t,e,i,n,o=this._form.getElementsByTagName("input"),s=o.length;for(this._handlingClick=!0,t=0;s>t;t++)e=o[t],i=this._layers[e.layerId],e.checked&&!this._map.hasLayer(i.layer)?(this._map.addLayer(i.layer),i.overlay||(n=i.layer)):!e.checked&&this._map.hasLayer(i.layer)&&this._map.removeLayer(i.layer);n&&(this._map.setZoom(this._map.getZoom()),this._map.fire("baselayerchange",{layer:n})),this._handlingClick=!1},_expand:function(){n.DomUtil.addClass(this._container,"leaflet-control-layers-expanded")},_collapse:function(){this._container.className=this._container.className.replace(" leaflet-control-layers-expanded","")}}),n.control.layers=function(t,e,i){return new n.Control.Layers(t,e,i)},n.PosAnimation=n.Class.extend({includes:n.Mixin.Events,run:function(t,e,i,o){this.stop(),this._el=t,this._inProgress=!0,this.fire("start"),t.style[n.DomUtil.TRANSITION]="all "+(i||.25)+"s cubic-bezier(0,0,"+(o||.5)+",1)",n.DomEvent.on(t,n.DomUtil.TRANSITION_END,this._onTransitionEnd,this),n.DomUtil.setPosition(t,e),n.Util.falseFn(t.offsetWidth),this._stepTimer=setInterval(n.bind(this.fire,this,"step"),50)},stop:function(){this._inProgress&&(n.DomUtil.setPosition(this._el,this._getPos()),this._onTransitionEnd(),n.Util.falseFn(this._el.offsetWidth))},_transformRe:/(-?[\d\.]+), (-?[\d\.]+)\)/,_getPos:function(){var e,i,o,s=this._el,a=t.getComputedStyle(s);return n.Browser.any3d?(o=a[n.DomUtil.TRANSFORM].match(this._transformRe),e=parseFloat(o[1]),i=parseFloat(o[2])):(e=parseFloat(a.left),i=parseFloat(a.top)),new n.Point(e,i,!0)},_onTransitionEnd:function(){n.DomEvent.off(this._el,n.DomUtil.TRANSITION_END,this._onTransitionEnd,this),this._inProgress&&(this._inProgress=!1,this._el.style[n.DomUtil.TRANSITION]="",clearInterval(this._stepTimer),this.fire("step").fire("end"))}}),n.Map.include({setView:function(t,e,i){e=this._limitZoom(e);var n=this._zoom!==e;if(this._loaded&&!i&&this._layers){this._panAnim&&this._panAnim.stop();var o=n?this._zoomToIfClose&&this._zoomToIfClose(t,e):this._panByIfClose(t);if(o)return clearTimeout(this._sizeTimer),this}return this._resetView(t,e),this},panBy:function(t,e,i){if(t=n.point(t),!t.x&&!t.y)return this;this._panAnim||(this._panAnim=new n.PosAnimation,this._panAnim.on({step:this._onPanTransitionStep,end:this._onPanTransitionEnd},this)),this.fire("movestart"),n.DomUtil.addClass(this._mapPane,"leaflet-pan-anim");var o=n.DomUtil.getPosition(this._mapPane).subtract(t)._round();return this._panAnim.run(this._mapPane,o,e||.25,i),this},_onPanTransitionStep:function(){this.fire("move")},_onPanTransitionEnd:function(){n.DomUtil.removeClass(this._mapPane,"leaflet-pan-anim"),this.fire("moveend")},_panByIfClose:function(t){var e=this._getCenterOffset(t)._floor();return this._offsetIsWithinView(e)?(this.panBy(e),!0):!1},_offsetIsWithinView:function(t,e){var i=e||1,n=this.getSize();return Math.abs(t.x)<=n.x*i&&Math.abs(t.y)<=n.y*i}}),n.PosAnimation=n.DomUtil.TRANSITION?n.PosAnimation:n.PosAnimation.extend({run:function(t,e,i,o){this.stop(),this._el=t,this._inProgress=!0,this._duration=i||.25,this._easeOutPower=1/Math.max(o||.5,.2),this._startPos=n.DomUtil.getPosition(t),this._offset=e.subtract(this._startPos),this._startTime=+new Date,this.fire("start"),this._animate()},stop:function(){this._inProgress&&(this._step(),this._complete())},_animate:function(){this._animId=n.Util.requestAnimFrame(this._animate,this),this._step()},_step:function(){var t=+new Date-this._startTime,e=1e3*this._duration;e>t?this._runFrame(this._easeOut(t/e)):(this._runFrame(1),this._complete())},_runFrame:function(t){var e=this._startPos.add(this._offset.multiplyBy(t));n.DomUtil.setPosition(this._el,e),this.fire("step")},_complete:function(){n.Util.cancelAnimFrame(this._animId),this._inProgress=!1,this.fire("end")},_easeOut:function(t){return 1-Math.pow(1-t,this._easeOutPower)}}),n.Map.mergeOptions({zoomAnimation:n.DomUtil.TRANSITION&&!n.Browser.android23&&!n.Browser.mobileOpera}),n.DomUtil.TRANSITION&&n.Map.addInitHook(function(){n.DomEvent.on(this._mapPane,n.DomUtil.TRANSITION_END,this._catchTransitionEnd,this)}),n.Map.include(n.DomUtil.TRANSITION?{_zoomToIfClose:function(t,e){if(this._animatingZoom)return!0;if(!this.options.zoomAnimation)return!1;var i=this.getZoomScale(e),o=this._getCenterOffset(t)._divideBy(1-1/i);if(!this._offsetIsWithinView(o,1))return!1;n.DomUtil.addClass(this._mapPane,"leaflet-zoom-anim"),this.fire("movestart").fire("zoomstart"),this.fire("zoomanim",{center:t,zoom:e});var s=this._getCenterLayerPoint().add(o);return this._prepareTileBg(),this._runAnimation(t,e,i,s),!0},_catchTransitionEnd:function(){this._animatingZoom&&this._onZoomTransitionEnd()},_runAnimation:function(t,e,i,o,s){this._animateToCenter=t,this._animateToZoom=e,this._animatingZoom=!0,n.Draggable&&(n.Draggable._disabled=!0);var a=n.DomUtil.TRANSFORM,r=this._tileBg;clearTimeout(this._clearTileBgTimer),n.Util.falseFn(r.offsetWidth);var h=n.DomUtil.getScaleString(i,o),l=r.style[a];r.style[a]=s?l+" "+h:h+" "+l},_prepareTileBg:function(){var t=this._tilePane,e=this._tileBg;if(e&&this._getLoadedTilesPercentage(e)>.5&&.5>this._getLoadedTilesPercentage(t))return t.style.visibility="hidden",t.empty=!0,this._stopLoadingImages(t),i;e||(e=this._tileBg=this._createPane("leaflet-tile-pane",this._mapPane),e.style.zIndex=1),e.style[n.DomUtil.TRANSFORM]="",e.style.visibility="hidden",e.empty=!0,t.empty=!1,this._tilePane=this._panes.tilePane=e;var o=this._tileBg=t;n.DomUtil.addClass(o,"leaflet-zoom-animated"),this._stopLoadingImages(o)},_getLoadedTilesPercentage:function(t){var e,i,n=t.getElementsByTagName("img"),o=0;for(e=0,i=n.length;i>e;e++)n[e].complete&&o++;return o/i},_stopLoadingImages:function(t){var e,i,o,s=Array.prototype.slice.call(t.getElementsByTagName("img"));for(e=0,i=s.length;i>e;e++)o=s[e],o.complete||(o.onload=n.Util.falseFn,o.onerror=n.Util.falseFn,o.src=n.Util.emptyImageUrl,o.parentNode.removeChild(o))},_onZoomTransitionEnd:function(){this._restoreTileFront(),n.DomUtil.removeClass(this._mapPane,"leaflet-zoom-anim"),n.Util.falseFn(this._tileBg.offsetWidth),this._animatingZoom=!1,this._resetView(this._animateToCenter,this._animateToZoom,!0,!0),n.Draggable&&(n.Draggable._disabled=!1)},_restoreTileFront:function(){this._tilePane.innerHTML="",this._tilePane.style.visibility="",this._tilePane.style.zIndex=2,this._tileBg.style.zIndex=1},_clearTileBg:function(){this._animatingZoom||this.touchZoom._zooming||(this._tileBg.innerHTML="")}}:{}),n.Map.include({_defaultLocateOptions:{watch:!1,setView:!1,maxZoom:1/0,timeout:1e4,maximumAge:0,enableHighAccuracy:!1},locate:function(t){if(t=this._locationOptions=n.extend(this._defaultLocateOptions,t),!navigator.geolocation)return this._handleGeolocationError({code:0,message:"Geolocation not supported."}),this;var e=n.bind(this._handleGeolocationResponse,this),i=n.bind(this._handleGeolocationError,this);return t.watch?this._locationWatchId=navigator.geolocation.watchPosition(e,i,t):navigator.geolocation.getCurrentPosition(e,i,t),this},stopLocate:function(){return navigator.geolocation&&navigator.geolocation.clearWatch(this._locationWatchId),this},_handleGeolocationError:function(t){var e=t.code,i=t.message||(1===e?"permission denied":2===e?"position unavailable":"timeout");this._locationOptions.setView&&!this._loaded&&this.fitWorld(),this.fire("locationerror",{code:e,message:"Geolocation error: "+i+"."})},_handleGeolocationResponse:function(t){var e=180*t.coords.accuracy/4e7,i=2*e,o=t.coords.latitude,s=t.coords.longitude,a=new n.LatLng(o,s),r=new n.LatLng(o-e,s-i),h=new n.LatLng(o+e,s+i),l=new n.LatLngBounds(r,h),u=this._locationOptions;if(u.setView){var c=Math.min(this.getBoundsZoom(l),u.maxZoom);this.setView(a,c)}this.fire("locationfound",{latlng:a,bounds:l,accuracy:t.coords.accuracy})}})})(this,document); \ No newline at end of file
diff --git a/chimere/templates/chimere/base.html b/chimere/templates/chimere/base.html
index 82f28c4..9cc7870 100644
--- a/chimere/templates/chimere/base.html
+++ b/chimere/templates/chimere/base.html
@@ -3,8 +3,8 @@
{% block extra_head %}
<link rel="stylesheet" href="{{ STATIC_URL }}chimere/css/styles.css" />
<link rel="stylesheet" href="{{ STATIC_URL }}chimere/css/print.css" media='print'/>
-{% if css_area %}
-<link rel="stylesheet" href="{{ css_area }}" />{% endif %}
+{% if css_map %}
+<link rel="stylesheet" href="{{ css_map }}" />{% endif %}
{% if MOBILE %}
<link rel="stylesheet" href="{{ STATIC_URL }}chimere/css/mobile.css" />
<script src="{{ STATIC_URL }}chimere/js/mobile.chimere.js" type="text/javascript"></script>{%endif%}
diff --git a/chimere/templates/chimere/blocks/actions.html b/chimere/templates/chimere/blocks/actions.html
index 93debb0..b00666f 100644
--- a/chimere/templates/chimere/blocks/actions.html
+++ b/chimere/templates/chimere/blocks/actions.html
@@ -1,3 +1,4 @@
+{% if actions.1 %}
<div id="topbar">
<ul id='action' class='action'>
{% for action, subactions in actions %}
@@ -18,3 +19,4 @@
{% endfor %}
</ul>
</div>
+{% endif %}
diff --git a/chimere/templates/chimere/blocks/areas.html b/chimere/templates/chimere/blocks/areas.html
deleted file mode 100644
index 268912c..0000000
--- a/chimere/templates/chimere/blocks/areas.html
+++ /dev/null
@@ -1,25 +0,0 @@
-{% load i18n %}
-{% if areas and areas.count > 1 %}
- <div id='areas-div'>
- <label for='areas-select'>{% trans "Maps" %}</label>
- <select id='areas-select'>
- {% if not has_default_area %}<option value=''>--</option>{% endif %}
- {% for area in areas %}
- <option value='{{area.urn}}'{% if area_name and area.urn == area_name %} selected='selected'{% endif %}{% if not area_name and area.default %} selected='selected'{% endif %}>{{area.name}}</option>
- {% endfor %}
- </select>
- </div>
- <script language='javascript' type='text/javascript'>
- jQuery('#areas-select').change(
- function (){
- val = jQuery(this).val();
- if(val){
- window.location = "{{ base_url }}" + jQuery(this).val();
- }
- else{
- window.location = "{{ base_url }}";
- }
- }
- );
- </script>
-{% endif %}
diff --git a/chimere/templates/chimere/blocks/areas_alternative.html b/chimere/templates/chimere/blocks/areas_alternative.html
deleted file mode 100644
index 837bf45..0000000
--- a/chimere/templates/chimere/blocks/areas_alternative.html
+++ /dev/null
@@ -1,11 +0,0 @@
-{% load i18n unlocalize_point %}
-{% if areas %}
- <div id='areas'>
- <h2>{% trans "Shortcuts"%}</h2>
- <ul>
- {% for area in areas %}
- <li><img class="zoom_image" alt="{% trans 'Zoom to' %} {{area.name}}" src="{{ STATIC_URL }}chimere/img/zoom.png" onclick="$('#maps').chimere('zoom', {'area': [{{area.upper_left_corner.x|unlocalize_point}}, {{area.upper_left_corner.y|unlocalize_point}}, {{area.lower_right_corner.x|unlocalize_point }}, {{area.lower_right_corner.y|unlocalize_point}}]});" /> {{area.name}}</li>
- {% endfor %}
- </ul>
- </div>
-{% endif %}
diff --git a/chimere/templates/chimere/blocks/head_chimere.html b/chimere/templates/chimere/blocks/head_chimere.html
index 7ed2b54..133192c 100644
--- a/chimere/templates/chimere/blocks/head_chimere.html
+++ b/chimere/templates/chimere/blocks/head_chimere.html
@@ -1,10 +1,9 @@
-{% for css_url in OSM_CSS_URLS %}
-<link rel="stylesheet" href="{{ css_url }}" />{% endfor %}
-{% for js_url in OSM_JS_URLS %}
+{% for css_url, condition in MAP_CONDITIONNAL_CSS_URLS %}
+{% if condition %}<!--[if {{condition}}]>{%endif%}<link rel="stylesheet" href="{{ css_url }}" />{% if condition %}<![endif]-->{%endif%}{% endfor %}
+{% if enable_clustering %}<script src="{{ STATIC_URL }}chimere/js/clustering.js" type="text/javascript"></script>{% endif %}
+{% for js_url in MAP_JS_URLS %}
<script src="{{ js_url }}" type="text/javascript"></script>{% endfor %}
{% if routing %}<script src="{{ STATIC_URL }}chimere/js/routing-widget.js" type="text/javascript"></script>{% endif %}
-{% if enable_clustering %}<script src="{{ STATIC_URL }}chimere/js/clustering.js" type="text/javascript"></script>{% endif %}
-<script src="{{ STATIC_URL }}chimere/js/jquery.chimere.js" type="text/javascript"></script>
<script type="text/javascript">
/* Global variables */
var STATIC_URL = static_url = "{{ STATIC_URL }}";
@@ -15,14 +14,15 @@
<script src="{{ STATIC_URL }}chimere/js/base.js" type="text/javascript"></script>
<script type="text/javascript">
var DEFAULT_ZOOM = {{ DEFAULT_ZOOM }};
+ {% ifequal CHIMERE_VIEW_RENDERER 'openlayers' %}
var EPSG_DISPLAY_PROJECTION = epsg_display_projection = new OpenLayers.Projection('EPSG:{{ EPSG_DISPLAY_PROJECTION }}');
OpenLayers.ImgPath = '{{ STATIC_URL }}chimere/img/';
var EPSG_PROJECTION = epsg_projection = new OpenLayers.Projection('EPSG:{{ EPSG_PROJECTION }}');
var CENTER_LONLAT = centerLonLat = new OpenLayers.LonLat{{ DEFAULT_CENTER }}.transform(epsg_display_projection, epsg_projection);
- /*var map_layer = {{ MAP_LAYER|safe }};*/
+ {% endifequal %}
var restricted_extent;
- {% if area_name %}
- var area_name = '{{ area_name }}';{% endif %}
+ {% if map_name %}
+ var map_name = '{{ map_name }}';{% endif %}
$(function() {$( ".draggable" ).draggable({handle:"h2"});});
var get_share_url = '{% url chimere:get-share-url %}';
diff --git a/chimere/templates/chimere/blocks/live_coordinates.html b/chimere/templates/chimere/blocks/live_coordinates.html
index 6d0dfb4..82166c8 100644
--- a/chimere/templates/chimere/blocks/live_coordinates.html
+++ b/chimere/templates/chimere/blocks/live_coordinates.html
@@ -13,12 +13,9 @@
chimere_init_options['dynamic_categories'] = false;
chimere_init_options['edition'] = true;
chimere_init_options["checked_categories"] = [];
- if({{default_area}}) chimere_init_options['selected_map_layer'] = {{default_area}};
+ if({{default_map}}) chimere_init_options['selected_map_layer'] = {{default_map}};
</script>
-</div>
-
-
<div id='map_edit'></div>
<div id='live_lonlat'>
<p>
@@ -30,6 +27,7 @@
<input type='text' name='live_longitude' id='live_longitude' size='8' adisabled='true' value='{{value_x|stringformat:"f"}}'/>
</p>
<input type='hidden' name='{{name}}' id='id_{{name}}' value='{{val}}'/>
+</div>
<script type="text/javascript">
function init_map_edit(){
diff --git a/chimere/templates/chimere/blocks/map.html b/chimere/templates/chimere/blocks/map.html
index 764d07b..a1faebf 100644
--- a/chimere/templates/chimere/blocks/map.html
+++ b/chimere/templates/chimere/blocks/map.html
@@ -25,17 +25,22 @@ $(function(){
{% if single_category %}
$(function() {$('#panel').hide()});{% endif %}
var chimere_init_options = {};
+ if (typeof simple != 'undefined'){
+ chimere_init_options['simple'] = simple;
+ }
{% if MOBILE %}chimere_init_options["mobile"] = true;{% endif %}
{% if enable_clustering %}chimere_init_options["enable_clustering"] = true;{% endif %}
chimere_init_options["default_icon"] = '{{STATIC_URL}}img/marker-green.png';
chimere_init_options["map_layers"] = [{{js_map_layers|safe|escape}}];
+ chimere_init_options["map_layer_names"] = [{{js_map_layer_names|safe|escape}}];
chimere_init_options['permalink_label'] = '{%trans "Permalink"%}';
chimere_init_options['permalink_element'] = document.getElementById('permalink');
chimere_init_options['routing'] = {{routing}};
{% if dynamic_categories %}chimere_init_options['dynamic_categories'] = true;{% endif %}
- {% if default_area %}
- chimere_init_options["default_area"] = new Array({{default_area.upper_left_corner.x}}, {{default_area.upper_left_corner.y}}, {{default_area.lower_right_corner.x}}, {{default_area.lower_right_corner.y}});
+ {% if default_map %}
+ chimere_init_options["default_map"] = new Array({{default_map.upper_left_corner.x}}, {{default_map.upper_left_corner.y}}, {{default_map.lower_right_corner.x}}, {{default_map.lower_right_corner.y}});
{% endif %}
+ {% if clustering_map %}chimere_init_options["clustering_map"] = true;{% endif %}
{% if p_zoom %}chimere_init_options["zoom"] = {{ p_zoom }};{% endif %}
{% if p_lat %}chimere_init_options["lat"] = {{ p_lat }};{% endif %}
{% if p_lon %}chimere_init_options["lon"] = {{ p_lon }};{% endif %}
@@ -46,12 +51,13 @@ $(function(){
{% if p_routing_steps %}chimere_init_options["routing_steps_lonlat"] = [{{ p_routing_steps }}];{% endif %}
{% if p_routing_transport %}chimere_init_options["routing_transport"] = "{{ p_routing_transport }}";{% endif %}
{% if p_routing_speed %}chimere_init_options["routing_speed"] = "{{ p_routing_speed }}";{% endif %}
+ {% ifequal map_renderer 'openlayers' %}
chimere_init_options["icon_offset"] = new OpenLayers.Pixel({{icon_offset_x}},
- {{icon_offset_y}});
+ {{icon_offset_y}});{% endifequal %}
chimere_init_options["dynamic_categories"] = {{ dynamic_categories }};
{% if p_display_submited %}chimere_init_options["display_submited"] = {{ p_display_submited }};{% endif %}
chimere_init_options["checked_categories"] = [{% for cc in checked_categories %}{% if forloop.counter0 > 0 %}, {% endif %}{{cc}}{% endfor %}];
- {% if area_id %}chimere_init_options['area_id'] = "{{area_id}}";{% endif %}
+ {% if map_id %}chimere_init_options['map_id'] = "{{map_id}}";{% endif %}
{% if p_current_feature %}
chimere_init_options["display_feature"] = {{ p_current_feature }};{% endif %}
{% if p_current_route %}
@@ -59,12 +65,13 @@ $(function(){
{% if restricted_extent %}{{ restricted_extent }}
chimere_init_options["restricted_extent"] = bounds;
{% endif %}
+ {% if enable_clustering %}chimere_init_options['enable_clustering'] = true;{% endif%}
{% if selected_map_layer %}
chimere_init_options["selected_map_layer"] = {{selected_map_layer}};
{% endif %}
- $("#main-map").chimere(chimere_init_options);
+ $("#{{map_id}}").chimere(chimere_init_options);
{% if zoom %}
- $('#maps').chimere('zoom', {'area':{{zoom}} });
+ $('#{{map_id}}').chimere('zoom', {'area':{{zoom}} });
{% endif %}
// init layer selection
diff --git a/chimere/templates/chimere/blocks/maps.html b/chimere/templates/chimere/blocks/maps.html
new file mode 100644
index 0000000..1d94092
--- /dev/null
+++ b/chimere/templates/chimere/blocks/maps.html
@@ -0,0 +1,29 @@
+{% load i18n %}
+{% if maps %}
+ <div id='maps-div'>
+ {% if maps|length > 1 %}
+ <label for='maps-select'>{% trans "Maps" %}</label>
+ <select id='maps-select'>
+ {% if not has_default_map %}<option value=''>--</option>{% endif %}
+ {% for map in maps %}
+ <option value='{{map.urn}}'{% if map_name and map.urn == map_name %} selected='selected'{% endif %}{% if not map_name and map.default %} selected='selected'{% endif %}>{{map.name}}</option>
+ {% endfor %}
+ </select>
+ <script language='javascript' type='text/javascript'>
+ jQuery('#maps-select').change(
+ function (){
+ val = jQuery(this).val();
+ if(val){
+ window.location = "{{ base_url }}" + jQuery(this).val();
+ }
+ else{
+ window.location = "{{ base_url }}";
+ }
+ }
+ );
+ </script>
+ {% else %}
+ <label for='maps-select'>{{maps.0.name}}</label>
+ {% endif %}
+ </div>
+{% endif %}
diff --git a/chimere/templates/chimere/blocks/maps_alternative.html b/chimere/templates/chimere/blocks/maps_alternative.html
new file mode 100644
index 0000000..c91d75a
--- /dev/null
+++ b/chimere/templates/chimere/blocks/maps_alternative.html
@@ -0,0 +1,11 @@
+{% load i18n unlocalize_point %}
+{% if maps %}
+ <div id='maps'>
+ <h2>{% trans "Shortcuts"%}</h2>
+ <ul>
+ {% for map in maps %}
+ <li><img class="zoom_image" alt="{% trans 'Zoom to' %} {{map.name}}" src="{{ STATIC_URL }}chimere/img/zoom.png" onclick="$('#maps').chimere('zoom', {'map': [{{map.upper_left_corner.x|unlocalize_point}}, {{map.upper_left_corner.y|unlocalize_point}}, {{map.lower_right_corner.x|unlocalize_point }}, {{map.lower_right_corner.y|unlocalize_point}}]});" /> {{map.name}}</li>
+ {% endfor %}
+ </ul>
+ </div>
+{% endif %}
diff --git a/chimere/templates/chimere/blocks/news.html b/chimere/templates/chimere/blocks/news.html
index fa581f7..07c2bbb 100644
--- a/chimere/templates/chimere/blocks/news.html
+++ b/chimere/templates/chimere/blocks/news.html
@@ -39,7 +39,7 @@ $(function(){
{% for property in news.getProperties %}
<p class='{{news.propertymodel.getNamedId}}'>{{ property.value|sanitize:"p b i br hr strong em span:style a:href:target ul li ol h1 h2 h3 h4 table td th tr"|safe }}</p>
{% endfor %}
- <p class='marker_link'><a href='{% get_tinyfied_url news area_name %}'>{% trans "See it on the map"%}</a></p>
+ <p class='marker_link'><a href='{% get_tinyfied_url news map_name %}'>{% trans "See it on the map"%}</a></p>
{% endif %}
</div>
{%endfor%}
diff --git a/chimere/templates/chimere/blocks/nominatim_widget.html b/chimere/templates/chimere/blocks/nominatim_widget.html
new file mode 100644
index 0000000..2971e60
--- /dev/null
+++ b/chimere/templates/chimere/blocks/nominatim_widget.html
@@ -0,0 +1,17 @@
+{% load i18n %}
+<div class='nominatim-widget'>
+<input type='hidden' name='nominatim_{{id}}_lat' id='nominatim_{{id}}_lat'/>
+<input type='hidden' name='nominatim_{{id}}_lon' id='nominatim_{{id}}_lon'/>
+<label for='nominatim_{{id}}' class='nominatim-search'>{% trans "Search:"%}</label>
+<input type='text' class='nominatim-widget' name='nominatim_{{id}}' id='nominatim_{{id}}' value=""/>
+<label class='nominatim-label' id='nominatim_{{id}}_label'>&nbsp;</label>
+</div>
+<script type='text/javascript'>
+var default_nominatim_lbl = "{% trans 'Street, City, Country'%}";
+var nominatim_url = "{{nominatim_url}}";
+$("#nominatim_{{id}}").val(default_nominatim_lbl);
+$("#nominatim_{{id}}").click(function(){
+ $("#nominatim_{{id}}").val('');
+});
+</script>
+
diff --git a/chimere/templates/chimere/blocks/submited.html b/chimere/templates/chimere/blocks/submited.html
index 5e4e546..7692d79 100644
--- a/chimere/templates/chimere/blocks/submited.html
+++ b/chimere/templates/chimere/blocks/submited.html
@@ -1,6 +1,19 @@
{% load i18n %}
+{% load url from future %}
<div class='edit' id='submited-window'>
- <p>{% trans "Your new proposition/modification has been submited. A moderator will treat your submission shortly. Thanks!" %}</p>
+ {% if is_modification %}
+ {% if can_write %}
+ <p>{% trans "Your modification has been taken into account." %}</p>
+ {% else %}
+ <p>{% trans "Your modification has been submited. A moderator will treat your submission shortly. Thanks!" %}</p>
+ {% endif %}
+ {% else %}
+ {% if can_write %}
+ <p>{% trans "Your new proposition has been added." %}</p>
+ {% else %}
+ <p>{% trans "Your new proposition has been submited. A moderator will treat your submission shortly. Thanks!" %}</p>
+ {% endif %}
+ {% endif %}
</div>
<script type='text/javascript'>
$(function(){
@@ -11,7 +24,7 @@
buttons:{
"{% trans "Add a new item" %}":
function() {
- window.location = "{{edit_url}}";
+ window.location = "{% url "chimere:edit" %}";
},
"{% trans "Continue edition of this item" %}":
function() {
@@ -19,7 +32,7 @@
},
"{% trans "Return to the map" %}":
function() {
- window.location = "{{index_url}}";
+ window.location = "{% url "chimere:index" %}";
}
}
});
diff --git a/chimere/templates/chimere/blocks/welcome.html b/chimere/templates/chimere/blocks/welcome.html
index 3ec2ccf..a0052f5 100644
--- a/chimere/templates/chimere/blocks/welcome.html
+++ b/chimere/templates/chimere/blocks/welcome.html
@@ -49,7 +49,7 @@ $(function(){
{% for property in news.getProperties %}
<p class='{{news.propertymodel.getNamedId}}'>{{ property.value|sanitize:"p b i br hr strong em img:src:alt span:style a:href:target ul li ol h1 h2 h3 h4 table td th tr"|safe }}</p>
{% endfor %}
- <p class='marker_link'><a href='{% get_tinyfied_url news area_name %}'>{% trans "See it on the map"%}</a></p>
+ <p class='marker_link'><a href='{% get_tinyfied_url news map_name %}'>{% trans "See it on the map"%}</a></p>
{% endif %}
</div>
{%endfor%}
diff --git a/chimere/templates/chimere/detail.html b/chimere/templates/chimere/detail.html
index abdd76e..002ecb3 100644
--- a/chimere/templates/chimere/detail.html
+++ b/chimere/templates/chimere/detail.html
@@ -21,7 +21,7 @@
<p class='description'>{{ marker.description|sanitize:"p b i br hr strong em img:src:alt span:style a:href:target ul li ol h1 h2 h3 h4 table td tr th"|safe}}</p>
{% endif %}
{% for property in marker.getProperties %}
- <p class='{{property.propertymodel.getNamedId}}'>{{ property.value|sanitize:"p b i br hr strong em img:src:alt span:style a:href:target ul li ol h1 h2 h3 h4 table td tr th"|safe}}</p>
+ <p class='property_{{property.propertymodel.slug}}'>{{ property.label|sanitize:"p b i br hr strong em img:src:alt span:style a:href:target ul li ol h1 h2 h3 h4 table td tr th"|safe}}</p>
{% endfor %}
{% if marker.origin %}<p class='detail_source'><strong>{% trans "Source:" %}</strong> <span>{{marker.origin}}</span></p>{% endif %}
{% if marker.license %}<p class='detail_license'><strong>{% trans "License:" %}</strong> <span>{{marker.license}}</span></p>{% endif %}
@@ -30,9 +30,12 @@
</p>{% endif %}
</div>
{% share_bar marker.name %}
- <p class='detail_amendment'><a href='{% if marker.route %}{% url chimere:editroute-item area_name_slash|default_if_none:"" marker.route.pk "" %}{%else%}{% url chimere:edit-item area_name_slash|default_if_none:"" marker.pk "" %}{%endif%}'>
- {% trans "Submit an amendment" %}
+ {% if can_edit or can_propose %}
+ <p class='detail_amendment'><a href='{% if marker.route %}{% url chimere:editroute-item map_name_slash|default_if_none:"" marker.route.pk "" %}{%else%}{% url chimere:edit-item map_name_slash|default_if_none:"" marker.pk "" %}{%endif%}'>
+ {% if can_write %}{% trans "Edit" %}{%else%}
+ {% trans "Submit an amendment" %}{%endif%}
</a>
+ {% endif %}
{% if moderator_emails %}
<a href="mailto:?from={{moderator_emails}}&subject={% trans "Propose amendment" %}&body={% trans "I would like to propose an amendment for this item:"%} {{share_url}}">
{% trans "Propose amendment" %}
diff --git a/chimere/templates/chimere/edit.html b/chimere/templates/chimere/edit.html
index 0e1b849..be97606 100644
--- a/chimere/templates/chimere/edit.html
+++ b/chimere/templates/chimere/edit.html
@@ -1,9 +1,9 @@
{% extends "chimere/base.html" %}
{% load i18n chimere_tags adminmedia inline_formset%}
{% block extra_head %}
- {{ form.media }}
+ {{ extra_head }}
{{ block.super }}
- {% head_chimere %}
+ {% head_chimere False %}
{% if dated %}
<!--script type="text/javascript" src="{{extra_url}}media/js/core.js"></script-->
<script type="text/javascript" src="{{ STATIC_URL }}chimere/js/utils.js"></script>
@@ -13,17 +13,20 @@
{% endif %}
{% endblock %}
+{% block body_class %}class='edit'{% endblock %}
+
{% block content %}
{{ block.super }}
- {% if submited %}{% submited %}{% endif %}
+ {% if submited %}{% include "chimere/blocks/submited.html" %}{% endif %}
{% if error_message %}<fieldset class='edit errorlist'>
<legend>{% trans "Error" %}</legend>
<p>{{ error_message }}</p>
- {% endif %}</fieldset>
- {% if is_modification and is_superuser %}<div class='warning'><p>{% trans "You are logged as an administrator. Your modifications will be taking into account immediately." %}</p></div>{% endif %}
+ </fieldset>{% endif %}
+ {% if is_modification and can_write %}<div class='warning'><p>{% trans "You have write rights for this map. Your modifications will be taking into account immediately." %}</p></div>{% endif %}
<fieldset class='edit'>
<legend>{% if is_modification %}{% trans "Modify a point of interest" %}{% else %}{% trans "Add a point of interest" %}{% endif %}</legend>
<form enctype="multipart/form-data" method='post' action='.'>
+ <p class='submit'><input type='submit' onclick='saveExtent();displayProgress();' value="{% if can_write%}{% trans 'Add/modify'%}{%else%}{% trans 'Propose'%}{%endif%}"/></p>
{% csrf_token %}
<div class='rightWrapper'>
<div class='rightform'>
@@ -31,6 +34,7 @@
<label for="id_point">{% trans "Point"%} *</label>
{%if form.point.errors %}<ul class="errorlist"><li>{% trans "Select a location for this new site" %}</li></ul>{%endif%}
{{point_widget}}
+ {{nominatim_widget}}
</div>
</div>
</div>
@@ -59,13 +63,13 @@
<p class="help">{{ form.keywords.help_text }}</p>
</div>
{% if dated %}
- <div class="fieldWrapper">
+ <div class="fieldWrapper dated_field">
<label for="id_start_date">{% trans "Start date" %}</label>
{{ form.start_date.errors }}
{{ form.start_date }}
<p class="help">{{ form.start_date.help_text }}</p>
</div>
- <div class="fieldWrapper">
+ <div class="fieldWrapper dated_field">
<label for="id_end_date">{% trans "End date" %}</label>
{{ form.end_date.errors }}
{{ form.end_date }}
@@ -100,7 +104,7 @@
cat_filters['{{subcat.id}}'].push('#id_{{property.getNamedId}}');
{% endfor %}
{% endfor %}
- $('#id_categories').change(function(){
+ function checkFilteredProperties(){
for (idx in cat_to_hide){
$(cat_to_hide[idx]).parent().hide();
}
@@ -111,11 +115,13 @@
$(cat_filters[val][ids]).parent().show();
}
}
- });
+ }
+ $('#id_categories').change(checkFilteredProperties);
$('#id_categories').change();
</script>
{% endif %}
</div>
+ {% if not is_authenticated %}
<div class='personalform'>
<h3>{% trans "Personal information"%}</h3>
<div class='warning'>
@@ -136,28 +142,48 @@
{% if form.submiter_comment.errors %}<div class='errors'>{{ form.submiter_comment.errors }}</div>{% endif %}
{{ form.submiter_comment }}
</div>
- </div>
+ </div>{% endif %}
<div id='upload_in_progress'>
<p>{% trans "Upload in progress. Please wait..." %}</p>
<p><img alt='Ajax loader' src='{{STATIC_URL}}chimere/img/ajax-loader.gif'/></p>
</div>
<script text='text/javascript'>
- $( "#upload_in_progress" ).dialog({
- modal: true,
- resizable:false,
- height:110,
- autoOpen:false,
- dialogClass:'alert-box'
- }
- );
+ $( "#upload_in_progress" ).dialog({
+ modal: true,
+ resizable:false,
+ height:110,
+ autoOpen:false,
+ dialogClass:'alert-box'
+ }
+ );
$(".ui-dialog-titlebar").hide();
function displayProgress(){
$("#upload_in_progress").dialog('open');
return true;
}
+ {% if dated %}
+ var dated_cats = new Array();
+ {% for cat in sub_categories %}{% for subcat in cat.1 %}{% if subcat.dated %}dated_cats.push('{{subcat.pk}}');{% endif %}{% endfor %}{% endfor %}
+ function checkDatedFields(){
+ {% if filtered_properties %}checkFilteredProperties();{% endif %}
+ var values = $('#id_categories').val();
+ displayed = false;
+ if (values){
+ for (idx=0;idx < values.length;idx++){
+ if (dated_cats.indexOf(values[idx]) != -1 ) displayed = true;
+ }
+ }
+ if (displayed){
+ $('.dated_field').show();
+ } else {
+ $('.dated_field').hide();
+ }
+ }
+ $('#id_categories').change(checkDatedFields);
+ $('#id_categories').change();
+ {% endif %}
</script>
- <p><input type='submit' onclick='saveExtent();displayProgress();' value="{% trans 'Propose'%}"/></p>
+ <p class='submit'><input type='submit' onclick='saveExtent();displayProgress();' value="{% if can_write%}{% trans 'Add/modify'%}{%else%}{% trans 'Propose'%}{%endif%}"/></p>
</form>
</fieldset>
- </div>
{% endblock %}
diff --git a/chimere/templates/chimere/edit_route.html b/chimere/templates/chimere/edit_route.html
index 10f4d5e..669be7a 100644
--- a/chimere/templates/chimere/edit_route.html
+++ b/chimere/templates/chimere/edit_route.html
@@ -13,9 +13,11 @@
{% endif %}
{% endblock %}
+{% block body_class %}class='edit'{% endblock %}
+
{% block content %}
{{ block.super }}
- {% if submited %}{% submited %}{% endif %}
+ {% if submited %}{% include "chimere/blocks/submited.html" %}{% endif %}
{% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}
{% if is_modification and is_superuser %}<div class='warning'><p>{% trans "You are logged as an administrator. Your modifications will be taking into account immediately." %}</p></div>{% endif %}
<fieldset class='edit'>
@@ -49,13 +51,13 @@
{{ form.description }}
</div>
{% if dated %}
- <div class="fieldWrapper">
+ <div class="fieldWrapper dated_field">
<label for="id_start_date">{% trans "Start date" %}</label>
{{ form.start_date.errors }}
{{ form.start_date }}
<p class="help">{{ form.start_date.help_text }}</p>
</div>
- <div class="fieldWrapper">
+ <div class="fieldWrapper dated_field">
<label for="id_end_date">{% trans "End date" %}</label>
{{ form.end_date.errors }}
{{ form.end_date }}
@@ -75,7 +77,30 @@
{% inline_formset "Multimedia files" formset_multi %}
{% inline_formset "Picture files" formset_picture %}
- <p><input type='submit' onclick='saveExtent();' value="{% trans 'Propose'%}"/></p>
+ <script text='text/javascript'>
+ {% if dated %}
+ var dated_cats = new Array();
+ {% for cat in sub_categories %}{% for subcat in cat.1 %}{% if subcat.dated %}dated_cats.push('{{subcat.pk}}');{% endif %}{% endfor %}{% endfor %}
+ function checkDatedFields(){
+ var values = $('#id_categories').val();
+ displayed = false;
+ if (values){
+ for (idx=0;idx < values.length;idx++){
+ if (dated_cats.indexOf(values[idx]) != -1 ) displayed = true;
+ }
+ }
+ if (displayed){
+ $('.dated_field').show();
+ } else {
+ $('.dated_field').hide();
+ }
+ }
+ $('#id_categories').change(checkDatedFields);
+ $('#id_categories').change();
+ {% endif %}
+ </script>
+
+ <p class='submit'><input type='submit' onclick='saveExtent();' value="{% trans 'Propose'%}"/></p>
</div>
</form>
</fieldset>
diff --git a/chimere/templates/chimere/feeds/rss.html b/chimere/templates/chimere/feeds/rss.html
index 53b0a49..df8bb26 100644
--- a/chimere/templates/chimere/feeds/rss.html
+++ b/chimere/templates/chimere/feeds/rss.html
@@ -46,13 +46,13 @@
{%ifequal category_rss_feed "area" %}
<h3>{% trans "New points of interest by area" %}</h3>
-{% if area_id %}
+{% if map_id %}
<div class="fieldWrapper">
- <h4>{% trans "Choose a pre-defined areas" %}</h4>
- <select name='id_area' id='id_area' onchange='document.forms["rss_form"].submit();'>
+ <h4>{% trans "Choose a pre-defined map" %}</h4>
+ <select name='id_map' id='id_map' onchange='document.forms["rss_form"].submit();'>
<option value="" selected="selected"> ---- </option>
- {% for areaID in area_id %}
- <option value="{{areaID.id}}">{{areaID.name}}</option>
+ {% for mapID in map_id %}
+ <option value="{{mapID.pk}}">{{mapID.name}}</option>
{% endfor %}
</select>
</div>
diff --git a/chimere/templates/chimere/main_map.html b/chimere/templates/chimere/main_map.html
index a3dec9a..7f19e79 100644
--- a/chimere/templates/chimere/main_map.html
+++ b/chimere/templates/chimere/main_map.html
@@ -1,10 +1,10 @@
{% extends "chimere/base.html" %}
{% load i18n unlocalize_point chimere_tags %}
+{% load url from future %}
{% block extra_head %}
{% head_jquery %}
{% head_chimere %}
{% head_jme %}
-<script src="{{ STATIC_URL }}chimere/js/jquery.chimere.js" type="text/javascript"></script>
{% if has_search %}
<script src="{{ STATIC_URL }}chimere/js/search.js" type="text/javascript"></script>
<script src="{{ STATIC_URL }}chimere/js/search-autocomplete.js" type="text/javascript"></script>{% endif %}
@@ -12,6 +12,19 @@
{% endblock %}
{% block message_edit %}{% endblock %}
{% block sidebar %}
+ {{block.super}}
+ <div id='auth'>
+ {% if not is_authenticated %}
+ <form action='.' method='post'>
+ <table>
+ {{auth_form.as_table}}
+ <tr><td></td><td><input type='submit' value='Ok'/></td></tr>
+ </table>
+ </form>
+ {% else %}
+ <p>{% trans "Identified as: " %}{{user}} - <a href='{% url "chimere:logout" %}'>{% trans "Logout"%}</a></p>
+ {% endif %}
+ </div>
<div id='panel' class='draggable ui-widget ui-corner-all'>
<h2 class='ui-widget ui-state-default ui-corner-all ui-widget-header'>{% trans "Categories"%}</h2>
<form method='post' name='frm_categories' id='frm_categories'>
@@ -28,14 +41,14 @@
</script>
<div id='utils-div' class='ui-widget ui-state-default ui-corner-all'>
- {% if areas_visible %}
- {% display_areas %}
+ {% if maps_visible %}
+ {% display_maps %}
{% endif %}
{% routing %}
{% display_welcome %}
{% display_news news_visible %}
<div id='permalink' class='ui-widget ui-button ui-state-default ui-corner-all'></div>
- <a id='simple_button' class='ui-widget ui-button ui-state-default ui-corner-all' href='{% url chimere:simple_index area_name_slash %}'>{% trans "Simple map" %}</a>
+ <a id='simple_button' class='ui-widget ui-button ui-state-default ui-corner-all' href='{% url "chimere:simple_index" map_name_slash %}'>{% trans "Simple map" %}</a>
</div>
<div id='detail' class='ui-widget ui-corner-all'></div>
{% endblock %}
diff --git a/chimere/templates/chimere/no_map.html b/chimere/templates/chimere/no_map.html
new file mode 100644
index 0000000..a078e5e
--- /dev/null
+++ b/chimere/templates/chimere/no_map.html
@@ -0,0 +1,41 @@
+{% extends "chimere/base.html" %}
+{% load i18n unlocalize_point chimere_tags %}
+{% load url from future %}
+{% block extra_head %}
+{% head_jquery %}
+{% head_chimere %}
+{% head_jme %}
+{{ block.super }}
+{% endblock %}
+{% block sidebar %}
+ {{block.super}}
+ <div id='auth'>
+ {% if not is_authenticated %}
+ <form action='.' method='post'>
+ <table>
+ {{auth_form.as_table}}
+ <tr><td></td><td><input type='submit' value='Ok'/></td></tr>
+ </table>
+ </form>
+ {% else %}
+ <p>{% trans "Identified as: " %}{{user}} - <a href='{% url "chimere:logout" %}'>{% trans "Logout"%}</a></p>
+ {% endif %}
+ </div>
+{% endblock %}
+{% block content %}
+ {{block.super}}
+ <div id='no-content'>
+ <div class='alert'>
+ <p>
+ {% if is_authenticated %}
+ {% trans "No map are currently available for your account." %}
+ {% else %}
+ {% trans "No map are currently available for public access. If you have an account identify yourself." %}
+ {% endif %}
+ </p>
+ </div>
+ </div>
+{% endblock %}
+{% block footer %}
+ <p class='map-footer'>{% include "chimere/blocks/footer.html" %}</p>
+{% endblock %}
diff --git a/chimere/templatetags/chimere_tags.py b/chimere/templatetags/chimere_tags.py
index ff0a9c8..e3b439f 100644
--- a/chimere/templatetags/chimere_tags.py
+++ b/chimere/templatetags/chimere_tags.py
@@ -14,23 +14,23 @@ from django.template import defaultfilters
from django.utils.translation import ugettext as _
from django.template.loader import render_to_string
-from chimere.models import Marker, Area, News, SubCategory, MultimediaType
+from chimere.models import Marker, Map, News, SubCategory, MultimediaType
from chimere.widgets import get_map_layers
log = getLogger(__name__)
register = template.Library()
-@register.inclusion_tag('chimere/blocks/areas.html', takes_context=True)
-def display_areas(context):
+@register.inclusion_tag('chimere/blocks/maps.html', takes_context=True)
+def display_maps(context):
"""
- Display available areas.
+ Display available maps.
"""
- context_data = {"areas": Area.getAvailable(),
+ context_data = {"maps": list(Map.getAvailable(user=context['user'])),
"base_url": reverse('chimere:index')
}
- if "area_name" in context:
- context_data['area_name'] = context["area_name"]
+ if "map_name" in context:
+ context_data['map_name'] = context["map_name"]
return context_data
@register.inclusion_tag('chimere/blocks/submited.html', takes_context=True)
@@ -43,11 +43,11 @@ def submited(context):
'contact_email':settings.CONTACT_EMAIL \
if hasattr(settings, 'CONTACT_EMAIL') else None}
-def get_news(area=None):
+def get_news(map=None):
# Retrieve news
news = News.objects.filter(available=True, is_front_page=True)
- if area:
- news = news.filter(Q(areas__isnull=True)|Q(areas__in=[area.pk]))
+ if map:
+ news = news.filter(Q(maps__isnull=True)|Q(maps__in=[map.pk]))
news = list(news.all())
if settings.CHIMERE_DAYS_BEFORE_EVENT:
# Retrieve active markers
@@ -60,8 +60,8 @@ def get_news(area=None):
if not'CHIMERE_ALL_DATED_ARE_FRONT' in dir(settings)\
or not settings.CHIMERE_ALL_DATED_ARE_FRONT:
q = q.filter(is_front_page=True)
- if area:
- q = q.filter(area.getIncludeMarker())
+ if map:
+ q = q.filter(map.getIncludeMarker())
news += list(q)
news.sort(key=lambda x:x.date, reverse=True)
return news
@@ -72,22 +72,22 @@ def display_welcome(context, display=False, title=''):
Welcome message and active news.
"""
context_data = {'display':display}
- area = None
- if "area_name" in context:
+ map = None
+ if "map_name" in context:
try:
- area = Area.objects.get(urn=context["area_name"])
- context_data['area_name'] = context['area_name']
- context_data['welcome_message'] = area.welcome_message
+ map = Map.objects.get(urn=context["map_name"])
+ context_data['map_name'] = context['map_name']
+ context_data['welcome_message'] = map.welcome_message
except ObjectDoesNotExist:
pass
else:
try:
- area = Area.objects.get(default=True)
- context_data['area_name'] = context['area_name']
- context_data['welcome_message'] = area.welcome_message
+ map = Map.objects.get(default=True)
+ context_data['map_name'] = context['map_name']
+ context_data['welcome_message'] = map.welcome_message
except ObjectDoesNotExist:
pass
- context_data['news_lst'] = get_news(area)[:3]
+ context_data['news_lst'] = get_news(map)[:3]
context_data['STATIC_URL'] = settings.STATIC_URL
context_data['title'] = title if title \
else _(u"Welcome to the %s") % settings.PROJECT_NAME
@@ -98,16 +98,16 @@ def display_news(context, title=''):
"""
All news.
"""
- area = None
+ map = None
context_data = {'STATIC_URL':settings.STATIC_URL}
- if "area_name" in context:
+ if "map_name" in context:
try:
- area = Area.objects.get(urn=context["area_name"])
- context_data['area_name'] = context['area_name']
- context_data['welcome_message'] = area.welcome_message
+ map = Map.objects.get(urn=context["map_name"])
+ context_data['map_name'] = context['map_name']
+ context_data['welcome_message'] = map.welcome_message
except ObjectDoesNotExist:
pass
- context_data['news_lst'] = get_news(area)
+ context_data['news_lst'] = get_news(map)
return context_data
@register.inclusion_tag('chimere/blocks/head_jquery.html', takes_context=True)
@@ -132,21 +132,23 @@ def head_jme(context):
return context_data
@register.inclusion_tag('chimere/blocks/head_chimere.html', takes_context=True)
-def head_chimere(context):
+def head_chimere(context, view=True):
"""
Create context and display head elements (js, css, etc.) for chimere.
"""
- area_name = context['area_name'] if 'area_name' in context else 'area_name'
- area = None
- if area_name:
+ map_name = context['map_name'] if 'map_name' in context else 'map_name'
+ map = None
+ if map_name:
try:
- area = Area.objects.get(urn=area_name)
+ map = Map.objects.get(urn=map_name)
except ObjectDoesNotExist:
pass
+ map_renderer = settings.CHIMERE_VIEW_RENDERER if view \
+ else settings.CHIMERE_EDIT_RENDERER
context_data = {
"STATIC_URL": settings.STATIC_URL,
"MEDIA_URL": settings.MEDIA_URL,
- "DYNAMIC_CATEGORIES": 'true' if area and area.dynamic_categories \
+ "DYNAMIC_CATEGORIES": 'true' if map and map.dynamic_categories \
else 'false',
"EXTRA_URL": reverse("chimere:index"),
"EPSG_DISPLAY_PROJECTION": settings.CHIMERE_EPSG_DISPLAY_PROJECTION,
@@ -154,7 +156,9 @@ def head_chimere(context):
"DEFAULT_CENTER": settings.CHIMERE_DEFAULT_CENTER,
"DEFAULT_ZOOM": settings.CHIMERE_DEFAULT_ZOOM,
"MAP_LAYER": settings.CHIMERE_DEFAULT_MAP_LAYER,
- "OSM_CSS_URLS": settings.OSM_CSS_URLS,
+ "CHIMERE_VIEW_RENDERER": map_renderer,
+ "MAP_CONDITIONNAL_CSS_URLS": settings.MAP_CONDITIONNAL_CSS_URLS[map_renderer],
+ "MAP_JS_URLS": settings.MAP_JS_URLS[map_renderer] if view else [],
'MOBILE':context['MOBILE'],
'routing': settings.CHIMERE_ENABLE_ROUTING \
if hasattr(settings, 'CHIMERE_ENABLE_ROUTING') else False,
@@ -199,59 +203,68 @@ def routing(context):
def map(context, map_id='map'):
context_data = {'map_id':map_id,
"STATIC_URL": settings.STATIC_URL,
+ "map_renderer":settings.CHIMERE_VIEW_RENDERER,
"enable_clustering":settings.CHIMERE_ENABLE_CLUSTERING}
context_data['icon_offset_x'] = settings.CHIMERE_ICON_OFFSET_X
context_data['icon_offset_y'] = settings.CHIMERE_ICON_OFFSET_Y
context_data['icon_width'] = settings.CHIMERE_ICON_WIDTH
context_data['icon_height'] = settings.CHIMERE_ICON_HEIGHT
+ context_data['enable_clustering'] = True \
+ if hasattr(settings, 'CHIMERE_ENABLE_CLUSTERING') and \
+ settings.CHIMERE_ENABLE_CLUSTERING \
+ else False
context_data['MOBILE'] = context['MOBILE']
context_data['routing'] = 'true' \
if hasattr(settings, 'CHIMERE_ENABLE_ROUTING') and \
settings.CHIMERE_ENABLE_ROUTING \
else 'false'
- area_name = context['area_name'] if 'area_name' in context else 'area_name'
- map_layers, default_area = get_map_layers(area_name)
+ map_name = context['map_name'] if 'map_name' in context else 'map_name'
+ map_layers, default_map = get_map_layers(map_name)
context_data['js_map_layers'] = ", ".join(
[js for name, js, default in map_layers])
+ context_data['js_map_layer_names'] = '"'+ '", "'.join(
+ [name for name, js, default in map_layers]) + '"'
context_data['map_layers'] = map_layers
- if default_area:
- context_data['selected_map_layer'] = default_area
+ if default_map:
+ context_data['selected_map_layer'] = default_map
context_data['checked_categories'] = []
- area = None
- if area_name:
+ map = None
+ if map_name:
try:
- area = Area.objects.get(urn=area_name)
+ map = Map.objects.get(urn=map_name)
except ObjectDoesNotExist:
pass
- if not area:
+ if not map:
try:
- area = Area.objects.get(default=True)
+ map = Map.objects.get(default=True)
except ObjectDoesNotExist:
pass
+ if map:
+ context_data['clustering_map'] = True if map.cluster else False
subcat_layer = SubCategory.objects.filter(as_layer=True, available=True)
- if area:
- if area.subcategories.count():
- subcat_layer = subcat_layer.filter(areas__pk=area.pk)
- context_data['area_id'] = area_name
+ if map:
+ if map.subcategories.count():
+ subcat_layer = subcat_layer.filter(maps__pk=map.pk)
+ context_data['map_id'] = map_name
if 'zoomout' in context and context['zoomout']:
context_data['zoom'] = "[%s]" % ",".join((
- unicode(area.upper_left_corner.x),
- unicode(area.upper_left_corner.y),
- unicode(area.lower_right_corner.x),
- unicode(area.lower_right_corner.y)))
- if area.subcategories.filter(available=True).count() == 1:
+ unicode(map.upper_left_corner.x),
+ unicode(map.upper_left_corner.y),
+ unicode(map.lower_right_corner.x),
+ unicode(map.lower_right_corner.y)))
+ if map.subcategories.filter(available=True).count() == 1:
context_data['single_category'] = True
- context_data['checked_categories'] = area.subcategories.all()[0].pk
- elif area.default_subcategories.count():
+ context_data['checked_categories'] = [map.subcategories.all()[0].pk]
+ elif map.default_subcategories.count():
context_data['checked_categories'] = [s.pk
- for s in area.default_subcategories.all()]
- if area.restrict_to_extent:
+ for s in map.default_subcategories.all()]
+ if map.restrict_to_extent:
context_data['restricted_extent'] = """
var bounds = new OpenLayers.Bounds();
bounds.extend(new OpenLayers.LonLat(%f, %f));
bounds.extend(new OpenLayers.LonLat(%f, %f));
-""" % (area.upper_left_corner.x, area.upper_left_corner.y,
- area.lower_right_corner.x, area.lower_right_corner.y)
+""" % (map.upper_left_corner.x, map.upper_left_corner.y,
+ map.lower_right_corner.x, map.lower_right_corner.y)
context_data['subcat_layer'], c_cat = [], None
for subcat in subcat_layer.order_by('category__order', 'category').all():
if subcat.category != c_cat:
@@ -265,9 +278,9 @@ bounds.extend(new OpenLayers.LonLat(%f, %f));
if SubCategory.objects.filter(available=True).count():
cat = unicode(SubCategory.objects.filter(available=True
).all()[0].pk)
- context_data['checked_categories'] = cat
+ context_data['checked_categories'] = [cat]
context_data['dynamic_categories'] = 'true' \
- if area and area.dynamic_categories else 'false'
+ if map and map.dynamic_categories else 'false'
if settings.CHIMERE_ROUTING_TRANSPORT:
context_data['p_routing_transport'] = settings.CHIMERE_ROUTING_TRANSPORT[0][0]
if settings.CHIMERE_ROUTING_SPEEDS and \
@@ -309,10 +322,10 @@ def alternate_multimedia(formset_multi, formset_picture):
'auto_type_id':MultimediaType.objects.get(name='auto').pk}
@register.simple_tag
-def get_tinyfied_url(marker, area_name=''):
+def get_tinyfied_url(marker, map_name=''):
if not marker or not hasattr(marker, 'get_absolute_url'):
return ""
- url = marker.get_absolute_url(area_name)
+ url = marker.get_absolute_url(map_name)
return url
@register.inclusion_tag('chimere/blocks/share_bar.html',
diff --git a/chimere/tests.py b/chimere/tests.py
index 3a3144e..e06b073 100644
--- a/chimere/tests.py
+++ b/chimere/tests.py
@@ -9,7 +9,7 @@ test_path = os.path.abspath(__file__)
test_dir_path = os.path.dirname(test_path) + os.sep
from django.conf import settings
-from django.contrib.auth.models import User
+from django.contrib.auth.models import User, Group
from django.contrib.gis.geos import GEOSGeometry
from django.contrib.messages.storage.fallback import FallbackStorage
from django.core.files import File
@@ -19,27 +19,12 @@ from django.test import TestCase
from django.test.client import Client
from chimere.admin import managed_modified, MarkerAdmin
-from chimere.models import Area, Icon, Importer, Category, SubCategory, Marker,\
- Route, News
-from chimere.forms import MarkerForm, AreaAdminForm
+from chimere.models import Icon, Importer, Category, SubCategory, Marker,\
+ Route, News, Map, MapUsers, MapGroups, PropertyModel
+from chimere.forms import MarkerForm, MapAdminForm
from chimere.templatetags.chimere_tags import display_news
from chimere.utils import ShapefileManager
-def areas_setup():
- area_1 = Area.objects.create(name='area 1', urn='area-1', order=1,
- available=True,
- upper_left_corner='SRID=4326;POINT(-4.907753 48.507656)',
- lower_right_corner='SRID=4326;POINT(-4.049447 48.279688)')
- area_2 = Area.objects.create(name='area 2', urn='area-2', order=2,
- available=True,
- upper_left_corner='SRID=4326;POINT(-3 47.5)',
- lower_right_corner='SRID=4326;POINT(-2.5 47)')
- area_3 = Area.objects.create(name='area 3', urn='area-3', order=3,
- available=True,
- upper_left_corner='SRID=4326;POINT(-1.5 1.5)',
- lower_right_corner='SRID=4326;POINT(1.5 -1.5)')
- return [area_1, area_2, area_3]
-
def subcategory_setup():
category = Category.objects.create(name='Main category',
available=True,
@@ -130,6 +115,48 @@ def route_setup(sub_categories=[]):
routes.append(route_2)
return routes
+def map_setup():
+ maps = []
+ maps.append(Map.objects.create(name='Map 1', available=True, order=1,
+ upper_left_corner='SRID=4326;POINT(-4.907753 48.507656)',
+ lower_right_corner='SRID=4326;POINT(-4.049447 48.279688)',
+ urn='map-1', default=False, public_read=True,
+ public_propose=True, public_write=False))
+ maps.append(Map.objects.create(name='Map 2', available=True, order=2,
+ upper_left_corner='SRID=4326;POINT(-3 47.5)',
+ lower_right_corner='SRID=4326;POINT(-2.5 47)',
+ urn='map-2', default=False, public_read=True,
+ public_propose=False, public_write=False))
+ maps.append(Map.objects.create(name='Map 3', available=True, order=3,
+ upper_left_corner='SRID=4326;POINT(-1.5 1.5)',
+ lower_right_corner='SRID=4326;POINT(1.5 -1.5)',
+ urn='map-3', default=False, public_read=False,
+ public_propose=False, public_write=False))
+ return maps
+
+def propertymodel_setup():
+ pms = []
+ pms.append(PropertyModel.objects.create(name='Address', order=10, available=False,
+ mandatory=False, slug='address', type='T'))
+ pms.append(PropertyModel.objects.create(name='City', order=20, available=False,
+ mandatory=False, slug='town', type='T'))
+ return pms
+
+def users_setup():
+ adminuser = User.objects.create_superuser('admin',
+ 'admin@test.com',
+ 'pass')
+ users = [adminuser]
+ users.append(User.objects.create_user('user1', 'user1@test.com',
+ 'pass'))
+ users.append(User.objects.create_user('user2', 'user2@test.com',
+ 'pass'))
+ groups = [Group.objects.create(name='groupname')]
+ users[2].groups.add(groups[0])
+ return users, groups
+
+areas_setup = users_setup
+
class ImporterTest:
def test_get(self):
nb_by_cat = {}
@@ -300,6 +327,21 @@ class GeoRSSImporterTest(TestCase, ImporterTest):
self.marker_importers = [(importer1, 1), (importer2, 32)]
+class CSVImporterTest(TestCase, ImporterTest):
+ def setUp(self):
+ subcategories = subcategory_setup()
+ pm = propertymodel_setup()
+ importer1 = Importer.objects.create(importer_type='CSV',
+ source=test_dir_path+'tests/test.csv')
+ importer1.categories.add(subcategories[0])
+ self.old_settings = settings.CHIMERE_NOMINATIM_FIELDS
+ settings.CHIMERE_NOMINATIM_FIELDS = {'street':'address',
+ 'city':'town', 'country':u'Great Britain'}
+ self.marker_importers = [(importer1, 2)]
+
+ def tearDown(self):
+ settings.CHIMERE_NOMINATIM_FIELDS = self.old_settings
+
class HtmlXsltImporterTest(TestCase, ImporterTest):
def setUp(self):
subcategories = subcategory_setup()
@@ -384,7 +426,7 @@ class MarkerFormTest(TestCase):
form = MarkerForm(data)
self.assertEqual(form.is_valid(), False)
-class AreaTest(TestCase):
+class MapTest(TestCase):
def setUp(self):
self.areas = areas_setup()
@@ -395,7 +437,7 @@ class AreaTest(TestCase):
response = self.client.get('/%s/' % area_1.urn)
self.assertRedirects(response, '/')
-class AreaAdminFormTest(TestCase):
+class MapAdminFormTest(TestCase):
def setUp(self):
self.areas = areas_setup()
@@ -405,7 +447,7 @@ class AreaAdminFormTest(TestCase):
area_1.save()
area_2.default = True
area_2.save()
- area_1 = Area.objects.get(urn=area_1.urn)
+ area_1 = Map.objects.get(urn=area_1.urn)
self.assertEqual(area_1.default, False)
def test_area_creation(self):
@@ -420,12 +462,12 @@ class AreaAdminFormTest(TestCase):
# order already given
data = base_data.copy()
data['order'] = self.areas[0].order
- form = AreaAdminForm(data)
+ form = MapAdminForm(data)
self.assertEqual(form.is_valid(), False)
# update an already existing area
data = base_data.copy()
data['order'] = self.areas[0].order
- form = AreaAdminForm(data, instance=self.areas[0])
+ form = MapAdminForm(data, instance=self.areas[0])
self.assertEqual(form.is_valid(), True)
# empty area
data = base_data.copy()
@@ -433,7 +475,7 @@ class AreaAdminFormTest(TestCase):
'upper_left_lon': 0,
'lower_right_lat': 0,
'lower_right_lon': 0})
- form = AreaAdminForm(data)
+ form = MapAdminForm(data)
self.assertEqual(form.is_valid(), False)
class DynamicCategoryTest(TestCase):
@@ -569,3 +611,43 @@ class RouteTest(TestCase):
route='SRID=4326;LINESTRING (30 10, 10 30, 40 40)',
has_associated_marker=False)
self.assertEqual(Marker.objects.filter(route=route_2).count(), 0)
+
+class PermissionTest(TestCase):
+ def setUp(self):
+ self.maps = map_setup()
+ self.users, self.groups = users_setup()
+ MapUsers.objects.create(map=self.maps[2], user=self.users[1], read=True,
+ propose=False, write=False)
+ MapGroups.objects.create(map=self.maps[2], group=self.groups[0],
+ read=True, propose=True, write=True)
+
+ def test_rights(self):
+ self.client.login(username=self.users[0].username, password='pass')
+ for urn, can_view, can_propose in [('map-1', True, True),
+ ('map-2', True, False),
+ ('map-3', False, False)]:
+ self.check_right(urn, can_view, can_propose, self.users[0].username)
+ self.client.login(username=self.users[1].username, password='pass')
+ for urn, can_view, can_propose in [('map-1', True, True),
+ ('map-2', True, False),
+ ('map-3', True, False)]:
+ self.check_right(urn, can_view, can_propose, self.users[1].username)
+ self.client.login(username=self.users[2].username, password='pass')
+ for urn, can_view, can_propose in [('map-1', True, True),
+ ('map-2', True, False),
+ ('map-3', True, True)]:
+ self.check_right(urn, can_view, can_propose, self.users[2].username)
+
+ def check_right(self, urn, can_view, can_propose, username):
+ url = reverse('chimere:index', args=[urn])
+ response = self.client.get(url)
+ status_code = 200 if can_view else 302
+ self.assertEqual(status_code, response.status_code,
+ msg='index for %s, %s: %s got where %s is attended' % (
+ username, urn, response.status_code, status_code))
+ url = reverse('chimere:edit', args=[urn+'/'])
+ status_code = 200 if can_propose else 302
+ response = self.client.get(url)
+ self.assertEqual(status_code, response.status_code,
+ msg='edit for %s, %s: %s got where %s is attended' % (
+ username, urn, response.status_code, status_code))
diff --git a/chimere/urls.py b/chimere/urls.py
index 28a7098..256c08f 100644
--- a/chimere/urls.py
+++ b/chimere/urls.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
-# Copyright (C) 2008-2014 Étienne Loks <etienne.loks_AT_peacefrogsDOTnet>
+# Copyright (C) 2008-2015 Étienne Loks <etienne.loks_AT_peacefrogsDOTnet>
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as
@@ -23,7 +23,7 @@ from django.contrib import admin
from django.core.exceptions import ImproperlyConfigured
admin.autodiscover()
-from chimere.models import Area
+from chimere.models import Map
from chimere.feeds import LatestPOIsByCategory, LatestPOIsBySubCategory, \
LatestPOIs, LatestPOIsByZone, LatestPOIsByZoneID
@@ -32,24 +32,24 @@ def i18n_javascript(request):
urlpatterns = patterns('chimere.views',
- url(r'^(?P<area_name>[a-zA-Z0-9_-]+/)?simple$', 'index', {'simple':True},
+ url(r'^(?P<map_name>[a-zA-Z0-9_-]+/)?simple$', 'index', {'simple':True},
name="simple_index")
)
if settings.CHIMERE_FEEDS:
urlpatterns += patterns('',
- url(r'^(?P<area_name>[a-zA-Z0-9_-]+/)?feeds$', 'chimere.views.rss',
+ url(r'^(?P<map_name>[a-zA-Z0-9_-]+/)?feeds$', 'chimere.views.rss',
name='feeds-form'),
- url(r'^(?P<area_name>[a-zA-Z0-9_-]+/)?feeds/category/(?P<category_id>\d+)$',
+ url(r'^(?P<map_name>[a-zA-Z0-9_-]+/)?feeds/category/(?P<category_id>\d+)$',
LatestPOIsByCategory(), name='feeds-cat'),
- url(r'^(?P<area_name>[a-zA-Z0-9_-]+/)?feeds/subcategory/(?P<category_id>\d+)$',
+ url(r'^(?P<map_name>[a-zA-Z0-9_-]+/)?feeds/subcategory/(?P<category_id>\d+)$',
LatestPOIsBySubCategory(), name='feeds-subcat'),
- url(r'^(?P<area_name>[a-zA-Z0-9_-]+/)?feeds/global/$', LatestPOIs(),
+ url(r'^(?P<map_name>[a-zA-Z0-9_-]+/)?feeds/global/$', LatestPOIs(),
name='feeds-global'),
- url(r'^(?P<area_name>[a-zA-Z0-9_-]+/)?feeds/area/(?P<area>[0-9-_.]+)$',
+ url(r'^(?P<map_name>[a-zA-Z0-9_-]+/)?feeds/area/(?P<area>[0-9-_.]+)$',
LatestPOIsByZone(), name='feeds-area'),
- url(r'^(?P<area_name>[a-zA-Z0-9_-]+/)?feeds/areaid/(?P<area_id>\d+)$',
- LatestPOIsByZoneID(), name='feeds-areaid'),
+ url(r'^(?P<map_name>[a-zA-Z0-9_-]+/)?feeds/mapid/(?P<map_id>\d+)$',
+ LatestPOIsByZoneID(), name='feeds-mapid'),
)
if hasattr(settings, 'CHIMERE_ENABLE_ROUTING') \
@@ -59,7 +59,7 @@ if hasattr(settings, 'CHIMERE_ENABLE_ROUTING') \
raise ImproperlyConfigured(u"CHIMERE_ROUTING_TRANSPORT must be set in"\
u" settings if you enable routing")
urlpatterns += patterns('chimere.views',
- url(r'^(?P<area_name>[a-zA-Z0-9_-]*/)?route/'\
+ url(r'^(?P<map_name>[a-zA-Z0-9_-]*/)?route/'\
r'(?P<transport>(%s))/((?P<speed>[0-9][0-9]*)/)?'
r'(?P<lon1>[-]?[0-9]+[.]?[0-9]*)_(?P<lat1>[-]?[0-9]+[.]?[0-9]*)_'\
r'(?P<lonlat_steps>([-]?[0-9]+[.]?[0-9]*_[-]?[0-9]+[.]?[0-9]*_)*)'\
@@ -85,42 +85,45 @@ if hasattr(settings, 'CHIMERE_SEARCH_ENGINE') \
#urlpatterns += [url(r'^search/', include('haystack.urls')),]
urlpatterns += patterns('chimere.views',
+ url(r'^logout/?$', 'logout_view', name="logout"),
url(r'^charte/?$', 'charte', name="charte"),
- url(r'^(?P<area_name>[a-zA-Z0-9_-]+/)?contact/?$', 'contactus',
+ url(r'^(?P<map_name>[a-zA-Z0-9_-]+/)?contact/?$', 'contactus',
name="contact"),
- url(r'^(?P<area_name>[a-zA-Z0-9_-]+/)?edit/$', 'edit',
+ url(r'^(?P<map_name>[a-zA-Z0-9_-]+/)?edit/$', 'edit',
name="edit"),
- url(r'^(?P<area_name>[a-zA-Z0-9_-]+/)?edit/(?P<item_id>\w+)/(?P<submited>\w+)?$',
+ url(r'^(?P<map_name>[a-zA-Z0-9_-]+/)?edit/(?P<item_id>\w+)/(?P<submited>\w+)?$',
'edit', name="edit-item"),
- url(r'^(?P<area_name>[a-zA-Z0-9_-]+/)?edit-route/$', 'editRoute',
+ url(r'^(?P<map_name>[a-zA-Z0-9_-]+/)?edit-route/$', 'editRoute',
name="editroute"),
- url(r'^(?P<area_name>[a-zA-Z0-9_-]+/)?edit-route/(?P<item_id>\w+)/(?P<submited>\w+)?$',
+ url(r'^(?P<map_name>[a-zA-Z0-9_-]+/)?edit-route/(?P<item_id>\w+)/(?P<submited>\w+)?$',
'editRoute', name="editroute-item"),
- url(r'^(?P<area_name>[a-zA-Z0-9_-]+/)?getDetail/(?P<marker_id>\d+)/?$', 'getDetail',
+ url(r'^(?P<map_name>[a-zA-Z0-9_-]+/)?getDetail/(?P<marker_id>\d+)/?$', 'getDetail',
name="get_detail"),
- url(r'^(?P<area_name>[a-zA-Z0-9_-]+/)?getDescriptionDetail/?(?P<category_id>\d+)/?$',
+ url(r'^(?P<map_name>[a-zA-Z0-9_-]+/)?getDescriptionDetail/?(?P<category_id>\d+)/?$',
'getDescriptionDetail', name="get_description_detail"),
- url(r'^(?P<area_name>[a-zA-Z0-9_-]+/)?getGeoObjects/'\
+ url(r'^(?P<map_name>[a-zA-Z0-9_-]+/)?getGeoObjects/'\
r'(?P<category_ids>[a-zA-Z0-9_-]+)(/(?P<status>\w+))?$', 'getGeoObjects',
name="getgeoobjects"),
- url(r'^(?P<area_name>[a-zA-Z0-9_-]+/)?get-marker/'\
- r'(?P<pk>[0-9]+)$', 'getMarker', name="get-marker"),
- url(r'^(?P<area_name>[a-zA-Z0-9_-]+/)?getAvailableCategories/$',
+ url(r'^(?P<map_name>[a-zA-Z0-9_-]+/)?getAvailableCategories/$',
'get_available_categories', name="get_categories"),
- url(r'^(?P<area_name>[a-zA-Z0-9_-]+/)?getAllCategories/$',
+ url(r'^(?P<map_name>[a-zA-Z0-9_-]+/)?get-marker/'\
+ r'(?P<pk>[0-9]+)$', 'getMarker', name="get-marker"),
+ url(r'^(?P<map_name>[a-zA-Z0-9_-]+/)?getAllCategories/$',
'get_all_categories', name="get_all_categories"),
- url(r'^(?P<area_name>[a-zA-Z0-9_-]+/)?getCategory/(?P<category_id>\d+)/?$',
+ url(r'^(?P<map_name>[a-zA-Z0-9_-]+/)?getCategory/(?P<category_id>\d+)/?$',
'getCategory', name="get_category"),
- url(r'^(?P<area_name>[a-zA-Z0-9_-]*/)?get-share-url/(?P<network>\w+)?$',
+ url(r'^(?P<map_name>[a-zA-Z0-9_-]*/)?get-share-url/(?P<network>\w+)?$',
'getShareUrl', name="get-share-url"),
- url(r'^(?P<area_name>[a-zA-Z0-9_-]*/)?ty/(?P<tiny_urn>\w+)$',
+ url(r'^(?P<map_name>[a-zA-Z0-9_-]*/)?ty/(?P<tiny_urn>\w+)$',
'redirectFromTinyURN', name="tiny"),
- url(r'^(?P<area_name>[a-zA-Z0-9_-]+/)?upload_file/((?P<category_id>\w+)/)?$',
+ url(r'^(?P<map_name>[a-zA-Z0-9_-]+/)?upload_file/((?P<category_id>\w+)/)?$',
'uploadFile', name='upload_file'),
- url(r'^(?P<area_name>[a-zA-Z0-9_-]+/)?process_route_file/(?P<file_id>\d+)/$',
+ url(r'^(?P<map_name>[a-zA-Z0-9_-]+/)?process_route_file/(?P<file_id>\d+)/$',
'processRouteFile', name='process_route_file'),
- url(r'^(?P<area_name>[a-zA-Z0-9_-]+/)?dyn/(?P<page_id>\w+)/$',
+ url(r'^(?P<map_name>[a-zA-Z0-9_-]+/)?dyn/(?P<page_id>\w+)/$',
'extraPage', name='extra_page'),
+ url(r'^(?P<map_name>[a-zA-Z0-9_-]+/)?json/(?P<app_name>[a-zA-Z0-9_-]+)/(?P<filename>[a-zA-Z0-9_-]+).json$', 'get_json',
+ name='get-json'),
# At the end, because it catches large
- url(r'^(?P<area_name>[a-zA-Z0-9_-]+)?', 'index', name="index"),
+ url(r'^(?P<map_name>[a-zA-Z0-9_-]+)?', 'index', name="index"),
)
diff --git a/chimere/utils.py b/chimere/utils.py
index 55fc45c..790fd56 100644
--- a/chimere/utils.py
+++ b/chimere/utils.py
@@ -24,10 +24,12 @@ Utilitaries
import csv
import datetime
import feedparser
+import simplejson as json
import os
import re
import StringIO
import tempfile
+from urllib import urlencode
import urllib2
import unicodedata
import zipfile
@@ -510,10 +512,18 @@ class CSVManager(ImportManager):
if msg:
return (0, 0, msg)
reader = csv.reader(source, delimiter=';', quotechar='"')
- prop_cols = []
- for pm in Marker.all_properties():
+ prop_cols, nominatim_fields = [], {}
+ reverse_nominatim_dct = dict((v, k)
+ for k, v in settings.CHIMERE_NOMINATIM_FIELDS.iteritems())
+ nominatim_default_query = settings.CHIMERE_NOMINATIM_FIELDS
+ for idx, pm in enumerate(Marker.all_properties()):
prop_cols.append((pm.name, pm.getAttrName(),
pm.getAttrName()+'_set'))
+ if settings.CHIMERE_NOMINATIM_FIELDS and \
+ pm.slug in reverse_nominatim_dct:
+ nominatim_fields[idx+len(self.COLS)] = \
+ reverse_nominatim_dct[pm.slug]
+ nominatim_default_query.pop(reverse_nominatim_dct[pm.slug])
cols = list(self.COLS) + prop_cols
datas = []
for idx, row in enumerate(reader):
@@ -521,7 +531,8 @@ class CSVManager(ImportManager):
try:
assert(len(row) >= len(cols))
except AssertionError:
- return (0, 0, _(u"Invalid CSV format"))
+ return (0, 0, _(u"Invalid CSV format - not enough columns "
+ u"check a reference CSV file"))
continue
if len(row) < len(cols):
continue
@@ -542,6 +553,22 @@ class CSVManager(ImportManager):
elif 'LINE' in geom:
cls = Route
dct['route'] = geom
+ elif settings.CHIMERE_NOMINATIM_FIELDS:
+ nominatim_query = settings.NOMINATIM_URL + "?"
+ nominatim_keys = nominatim_default_query.copy()
+ nominatim_keys['format'] = 'json'
+ for idx in nominatim_fields:
+ nominatim_keys[nominatim_fields[idx]] = row[idx]
+ nominatim_query += urlencode(nominatim_keys)
+ remotehandle = urllib2.urlopen(nominatim_query)
+ result = StringIO.StringIO(remotehandle.read())
+ remotehandle.close()
+ result = json.load(result)
+ if not result:
+ continue
+ result = result[0]
+ cls = Marker
+ dct['point'] = "POINT(%s %s)" % (result['lon'], result['lat'])
else:
continue
import_key = pk if pk else name.decode('utf-8')
@@ -559,12 +586,14 @@ class CSVManager(ImportManager):
return (new_item, updated_item, msg)
@classmethod
- def export(cls, queryset):
+ def export(cls, queryset, cols=[]):
dct = {'description':unicode(datetime.date.today()), 'data':[]}
cls_name = queryset.model.__name__.lower()
- cols = list(cls.COLS)
- for pm in queryset.model.all_properties():
- cols.append((pm.name, pm.getAttrName(), pm.getAttrName()+'_set'))
+ if not cols:
+ cols = list(cls.COLS)
+ if hasattr(queryset.model, 'all_properties'):
+ for pm in queryset.model.all_properties():
+ cols.append((pm.name, pm.getAttrName(), pm.getAttrName()+'_set'))
header = [col[0] for col in cols]
dct['data'].append(header)
for item in queryset.all():
@@ -573,7 +602,10 @@ class CSVManager(ImportManager):
if callable(attr):
data.append(attr(item))
else:
- data.append(getattr(item, attr))
+ v = getattr(item, attr)
+ if v == None:
+ v = ''
+ data.append(v)
dct['data'].append(data)
filename = unicode_normalize(settings.PROJECT_NAME + dct['description']\
+ '.csv')
diff --git a/chimere/views.py b/chimere/views.py
index 5beb5fd..4ca2498 100644
--- a/chimere/views.py
+++ b/chimere/views.py
@@ -24,12 +24,15 @@
Views of the project
"""
+import copy
import datetime
from itertools import groupby
import re
import simplejson as json
from django.conf import settings
+from django.contrib.auth import authenticate, login, logout
+from django.contrib.auth.forms import AuthenticationForm
from django.contrib.gis.geos import GEOSGeometry
from django.contrib.gis.gdal.error import OGRException
from django.contrib.gis.measure import D
@@ -45,16 +48,16 @@ from django.utils import simplejson as json
from django.utils.http import urlquote
from django.utils.translation import ugettext as _
-from chimere.actions import actions
+from chimere.actions import actions as default_actions
from chimere.models import Category, SubCategory, PropertyModel, Page,\
- Marker, Route, News, SimpleArea, Area, Color, TinyUrl, RouteFile,\
+ Marker, Route, News, SimpleArea, Map, Color, TinyUrl, RouteFile,\
AggregatedRoute
-from chimere.widgets import getMapJS, PointChooserWidget, \
+from chimere.widgets import getMapJS, PointChooserWidget, NominatimWidget,\
RouteChooserWidget, AreaWidget
from chimere.forms import MarkerForm, RouteForm, ContactForm, FileForm, \
FullFileForm, MultimediaFileFormSet, PictureFileFormSet, notifySubmission,\
- notifyStaff, AreaForm, RoutingForm, getStaffEmails
+ notifyStaff, MapForm, RoutingForm, getStaffEmails
from chimere.route import router
@@ -71,43 +74,41 @@ def get_base_uri(request):
return base_uri
#TODO: convert to requestcontext
-def get_base_response(request, area_name=""):
+def get_base_response(request, map_name="", propose=False):
"""
Get the base url
"""
- base_response_dct = {'media_path':settings.MEDIA_URL,}
+ base_response_dct = {'media_path':settings.MEDIA_URL,
+ 'is_authenticated':request.user.is_authenticated()}
base_response_dct['MOBILE'] = settings.MOBILE_TEST or \
get_current_site(request).domain in settings.MOBILE_DOMAINS
base_url = reverse("chimere:index")
if not base_url.startswith('/'):
base_url = '/' + base_url
- if area_name and area_name.endswith('/'):
- area_name = area_name[:-1]
- if area_name:
- base_response_dct['area_name_slash'] = area_name + "/"
+ if map_name and map_name.endswith('/'):
+ map_name = map_name[:-1]
+ if map_name:
+ base_response_dct['map_name_slash'] = map_name + "/"
if base_url[-1] != '/':
base_url += '/'
- base_url += area_name + '/'
+ base_url += map_name + '/'
base_response_dct['extra_url'] = base_url
- area = None
- if area_name:
- try:
- area = Area.objects.get(urn=area_name, available=True)
- except ObjectDoesNotExist:
- return None, redirect(reverse('chimere:index'))
- else:
- try:
- area = Area.objects.get(default=True)
- area_name = area.urn
- except ObjectDoesNotExist:
- pass
+ map = Map.getAvailable(user=request.user, urn=map_name, propose=propose,
+ single=True)
+ if map:
+ map_name = map.urn
+ elif map_name:
+ return None, redirect(reverse('chimere:index'))
+ if map:
+ base_response_dct['can_write'] = map.can_write(user=request.user)
+ base_response_dct['can_propose'] = map.can_propose(user=request.user)
- base_response_dct['area'] = area
- base_response_dct['area_name'] = area_name
- if area and area.external_css:
- base_response_dct['css_area'] = area.external_css
+ base_response_dct['map'] = map
+ base_response_dct['map_name'] = map_name
+ if map and map.external_css:
+ base_response_dct['css_map'] = map.external_css
base_response_dct['dynamic_categories'] = True \
- if area and area.dynamic_categories else False
+ if map and map.dynamic_categories else False
base_response_dct['JQUERY_JS_URLS'] = settings.JQUERY_JS_URLS
base_response_dct['JQUERY_CSS_URLS'] = settings.JQUERY_CSS_URLS
base_response_dct['PROJECT_NAME'] = settings.PROJECT_NAME
@@ -115,17 +116,17 @@ def get_base_response(request, area_name=""):
base_response_dct['EXTRA_CSS'] = settings.EXTRA_CSS
return base_response_dct, None
-def getShareUrl(request, area_name='', network=''):
+def getShareUrl(request, map_name='', network=''):
"""
Get a share url
"""
- data = getTinyfiedUrl(request, request.GET.urlencode(), area_name)
+ data = getTinyfiedUrl(request, request.GET.urlencode(), map_name)
for name, url, img in settings.CHIMERE_SHARE_NETWORKS:
if defaultfilters.slugify(name) == network:
return HttpResponse(url % {'text':data['text'], 'url':data['url']})
return HttpResponse('')
-def getShareNetwork(request, area_name='', marker=None):
+def getShareNetwork(request, map_name='', marker=None):
"""
Get URLs to share items
"""
@@ -134,14 +135,19 @@ def getShareNetwork(request, area_name='', marker=None):
parameters = u'current_feature=%d' % marker.pk
parameters += u"&checked_categories=%s" % "_".join([str(m.id) \
for m in marker.categories.all()])
- net_dct = getTinyfiedUrl(request, parameters, area_name)
+ net_dct = getTinyfiedUrl(request, parameters, map_name)
share_networks = []
for network in settings.CHIMERE_SHARE_NETWORKS:
share_networks.append((network[0], network[1] % net_dct, network[2]))
return share_networks, net_dct
-def index(request, area_name=None, default_area=None, simple=False,
- get_response=False):
+
+def logout_view(request):
+ logout(request)
+ return redirect(reverse('chimere:index'))
+
+def index(request, map_name=None, default_map=None, simple=False,
+ get_response=False, actions=default_actions):
"""
Main page
"""
@@ -153,9 +159,28 @@ def index(request, area_name=None, default_area=None, simple=False,
request.session['last_visit'] != today:
request.session['last_visit'] = today
news_visible = True
- response_dct, redir = get_base_response(request, area_name)
+ response_dct, redir = get_base_response(request, map_name)
if redir:
return redir
+
+ if request.POST:
+ auth_form = AuthenticationForm(None, request.POST)
+ invalid_msg = _(u"Invalid user or password.")
+ if auth_form.is_valid():
+ user = authenticate(
+ username=auth_form.cleaned_data['username'],
+ password=auth_form.cleaned_data['password'])
+ if user is not None and user.is_active:
+ login(request, user)
+ return redirect(reverse('chimere:index'))
+ else:
+ response_dct['auth_form'] = auth_form
+ else:
+ response_dct['auth_form'] = AuthenticationForm()
+
+ if not response_dct['map']:
+ return render_to_response('chimere/no_map.html', response_dct,
+ context_instance=RequestContext(request))
# don't mess with permalink
zoomout = True
if request.GET and 'lat' in request.GET \
@@ -173,23 +198,24 @@ def index(request, area_name=None, default_area=None, simple=False,
except:
pass
response_dct.update({
- 'actions':actions(response_dct['area_name']),
+ 'actions':actions(request.user, response_dct['map_name']),
'action_selected':('view',),
'error_message':'',
'is_map':True,
'news_visible': news_visible,
- 'areas_visible': settings.CHIMERE_DISPLAY_AREAS,
+ 'maps_visible': settings.CHIMERE_DISPLAY_MAPS,
'map_layer':settings.CHIMERE_DEFAULT_MAP_LAYER,
'dynamic_categories':response_dct['dynamic_categories'],
'zoomout':zoomout,
- 'has_default_area':Area.objects.filter(default=True).count(),
+ 'has_default_map':Map.objects.filter(default=True).count(),
+ 'zoomout':zoomout,
+ 'has_search':settings.CHIMERE_SEARCH_ENGINE,
'zoomout':zoomout,
- 'has_search':settings.CHIMERE_SEARCH_ENGINE
})
if hasattr(settings, 'CONTACT_EMAIL') and settings.CONTACT_EMAIL:
response_dct['contact_email'] = settings.CONTACT_EMAIL
response_dct['share_networks'], net_dct = \
- getShareNetwork(request, response_dct['area_name'])
+ getShareNetwork(request, response_dct['map_name'])
tpl = 'chimere/main_map.html'
response_dct['simple'] = simple
if simple:
@@ -205,13 +231,13 @@ def get_edit_page(redirect_url, item_cls, item_form,
"""
Edition page
"""
- def func(request, area_name="", item_id=None, cat_type=['M']):
- response_dct, redir = get_base_response(request, area_name)
+ def func(request, map_name="", item_id=None, cat_type=['M']):
+ response_dct, redir = get_base_response(request, map_name, propose=True)
if redir:
return redir, None, None
- if 'area_name' in response_dct:
- area_name = response_dct['area_name']
- subcategories = SubCategory.getAvailable(cat_type, area_name,
+ if 'map_name' in response_dct:
+ map_name = response_dct['map_name']
+ subcategories = SubCategory.getAvailable(cat_type, map_name,
public=True)
listed_subcats = []
if subcategories:
@@ -224,7 +250,7 @@ def get_edit_page(redirect_url, item_cls, item_form,
try:
init_item = item_cls.objects.get(pk=item_id)
except:
- return redirect(redirect_url, area_name + '/' if area_name \
+ return redirect(redirect_url, map_name + '/' if map_name \
else ''), None, None
ref_item = init_item
modified_item = item_cls.objects.filter(ref_item=init_item,
@@ -235,18 +261,17 @@ def get_edit_page(redirect_url, item_cls, item_form,
init_multi = init_item.get_init_multi() if init_item else None
init_picture = init_item.get_init_picture() if init_item else None
- if init_item and not request.user.is_superuser and \
+ if init_item and not response_dct['can_write'] and \
not init_item.submiter_session_key == \
request.session.session_key:
# hide personal information
for k in ('submiter_name', 'submiter_email', 'submiter_comment'):
setattr(init_item, k, '')
- response_dct['is_superuser'] = request.user.is_superuser
# If the form has been submited
if request.method == 'POST':
inst = None
- # allow to directly modify only if owner or superuser
- if init_item and (request.user.is_superuser or \
+ # allow to directly modify only if owner or has can_write permission
+ if init_item and (response_dct['can_write'] or \
init_item.submiter_session_key == \
request.session.session_key):
inst = init_item
@@ -259,7 +284,7 @@ def get_edit_page(redirect_url, item_cls, item_form,
# All validation rules pass
if form.is_valid() and formset_multi.is_valid() and \
formset_picture.is_valid():
- item = form.save()
+ item = form.save(can_write=response_dct.get('can_write'))
# set the session key (to permit modifications)
item.submiter_session_key = request.session.session_key
@@ -277,7 +302,7 @@ def get_edit_page(redirect_url, item_cls, item_form,
# just submited
if not item.status:
- item.status = 'S'
+ item.status = 'A' if response_dct.get('can_write') else 'S'
item.save()
marker = item
@@ -293,9 +318,9 @@ def get_edit_page(redirect_url, item_cls, item_form,
f.save(marker)
base_uri = get_base_uri(request)
notifySubmission(base_uri, item)
- response_dct = get_base_response(request, area_name)
+ response_dct = get_base_response(request, map_name)
return redirect(redirect_url + '-item',
- area_name + '/' if area_name else '',
+ map_name + '/' if map_name else '',
item.ref_item.pk, 'submited'), None, subcategories
else:
response_dct['error_message'] = _(u"There are missing field(s)"
@@ -312,11 +337,12 @@ def get_edit_page(redirect_url, item_cls, item_form,
get_edit_marker = get_edit_page('chimere:edit', Marker, MarkerForm)
-def edit(request, area_name="", item_id=None, submited=False):
+def edit(request, map_name="", item_id=None, submited=False,
+ actions=default_actions):
"""
Edition page
"""
- response, values, sub_categories = get_edit_marker(request, area_name,
+ response, values, sub_categories = get_edit_marker(request, map_name,
item_id, ['M', 'B'])
if response:
return response
@@ -330,20 +356,24 @@ def edit(request, area_name="", item_id=None, submited=False):
point_value = init_item.point if init_item else None
if request.POST and request.POST.get('point'):
point_value = request.POST.get('point')
+ has_dated_items = settings.CHIMERE_DAYS_BEFORE_EVENT and [
+ True for cat, subcats in sub_categories
+ if [True for subcat in subcats if subcat.dated]]
response_dct.update({
- 'actions':actions(response_dct['area_name']),
+ 'actions':actions(request.user, response_dct['map_name']),
'action_selected':('contribute', 'edit'),
'map_layer':settings.CHIMERE_DEFAULT_MAP_LAYER,
'form':form,
'formset_multi':formset_multi,
'formset_picture':formset_picture,
- 'dated':settings.CHIMERE_DAYS_BEFORE_EVENT,
- 'extra_head':form.media,
+ 'dated':has_dated_items,
+ 'extra_head':form.media + NominatimWidget().media,
'marker_id':item_id,
'sub_categories':sub_categories,
'point_widget':PointChooserWidget().render('point',
point_value,
- area_name=response_dct['area_name']),
+ map_name=response_dct['map_name']),
+ 'nominatim_widget':NominatimWidget().render('point'),
'properties':declared_fields,
'filtered_properties':filtered_properties,
'submited':submited
@@ -354,8 +384,8 @@ def edit(request, area_name="", item_id=None, submited=False):
return render_to_response('chimere/edit.html', response_dct,
context_instance=RequestContext(request))
-def uploadFile(request, category_id='', area_name=''):
- response_dct, redir = get_base_response(request, area_name)
+def uploadFile(request, category_id='', map_name=''):
+ response_dct, redir = get_base_response(request, map_name)
if redir:
return redir
Form = FileForm if not category_id else FullFileForm
@@ -402,7 +432,7 @@ def uploadFile(request, category_id='', area_name=''):
return render_to_response('chimere/upload_file.html', response_dct,
context_instance=RequestContext(request))
-def processRouteFile(request, area_name='', file_id=None):
+def processRouteFile(request, map_name='', file_id=None):
if file_id:
try:
route_file = RouteFile.objects.get(pk=file_id)
@@ -420,11 +450,12 @@ def processRouteFile(request, area_name='', file_id=None):
get_edit_route = get_edit_page('chimere:editroute', Route, RouteForm)
-def editRoute(request, area_name="", item_id=None, submited=False):
+def editRoute(request, map_name="", item_id=None, submited=False,
+ actions=default_actions):
"""
Route edition page
"""
- response, values, sub_categories = get_edit_route(request, area_name,
+ response, values, sub_categories = get_edit_route(request, map_name,
item_id, ['R', 'B'])
if response:
return response
@@ -438,19 +469,22 @@ def editRoute(request, area_name="", item_id=None, submited=False):
route_value = init_item.route if init_item else None
if request.POST and request.POST.get('route'):
route_value = request.POST.get('route')
+ has_dated_items = settings.CHIMERE_DAYS_BEFORE_EVENT and [
+ True for cat, subcats in sub_categories
+ if [True for subcat in subcats if subcat.dated]]
response_dct.update({
- 'actions':actions(response_dct['area_name']),
+ 'actions':actions(request.user, response_dct['map_name']),
'action_selected':('contribute', 'edit-route'),
'error_message':'',
'map_layer':settings.CHIMERE_DEFAULT_MAP_LAYER,
'form':form,
'formset_multi':formset_multi,
'formset_picture':formset_picture,
- 'dated':settings.CHIMERE_DAYS_BEFORE_EVENT,
+ 'dated':has_dated_items,
'extra_head':form.media,
'sub_categories':sub_categories,
'route_widget':RouteChooserWidget().render('route', route_value,
- area_name=response_dct['area_name'], routefile_id='',),
+ map_name=response_dct['map_name'], routefile_id='',),
'properties':declared_fields,
'submited':submited
})
@@ -460,34 +494,33 @@ def editRoute(request, area_name="", item_id=None, submited=False):
return render_to_response('chimere/edit_route.html', response_dct,
context_instance=RequestContext(request))
-def submited(request, area_name="", action=""):
+def submited(request, map_name="", action="", actions=default_actions):
"""
Successful submission page
"""
- response_dct, redir = get_base_response(request, area_name)
+ response_dct, redir = get_base_response(request, map_name)
if redir:
return redir
- dct = {'actions':actions(response_dct['area_name']),
- 'action_selected':action,}
+ response_dct.update({'actions':actions(request.user, response_dct['map_name']),
+ 'action_selected':action,})
if hasattr(settings, 'CONTACT_EMAIL') and settings.CONTACT_EMAIL:
response_dct['contact_email'] = settings.CONTACT_EMAIL
- response_dct.update(dct)
return render_to_response('chimere/submited.html', response_dct,
context_instance=RequestContext(request))
-def charte(request, area_name=""):
+def charte(request, map_name="", actions=default_actions):
"""
Affichage de la charte
"""
- response_dct, redir = get_base_response(request, area_name)
+ response_dct, redir = get_base_response(request, map_name)
if redir:
return redir
- response_dct.update({'actions':actions(response_dct['area_name']),
+ response_dct.update({'actions':actions(request.user, response_dct['map_name']),
'action_selected':('charte',)})
return render_to_response('chimere/charte.html', response_dct,
context_instance=RequestContext(request))
-def contactus(request, area_name=""):
+def contactus(request, map_name="", actions=default_actions):
"""
Contact page
"""
@@ -508,16 +541,16 @@ def contactus(request, area_name=""):
msg = _(u"Temporary error. Renew your message later.")
else:
form = ContactForm()
- response_dct, redir = get_base_response(request, area_name)
+ response_dct, redir = get_base_response(request, map_name)
if redir:
return redir
- response_dct.update({'actions':actions(response_dct['area_name']),
+ response_dct.update({'actions':actions(request.user, response_dct['map_name']),
'action_selected':('contact',),
'contact_form':form, 'message':msg})
return render_to_response('chimere/contactus.html', response_dct,
context_instance=RequestContext(request))
-def extraPage(request, area_name="", page_id=""):
+def extraPage(request, map_name="", page_id="", actions=default_actions):
"""
Extra dynamic pages
"""
@@ -525,10 +558,10 @@ def extraPage(request, area_name="", page_id=""):
page = Page.objects.get(available=True, mnemonic=page_id)
except ObjectDoesNotExist:
return redirect(reverse('chimere:index'))
- response_dct, redir = get_base_response(request, area_name)
+ response_dct, redir = get_base_response(request, map_name)
if redir:
return redir
- response_dct.update({'actions':actions(response_dct['area_name']),
+ response_dct.update({'actions':actions(request.user, response_dct['map_name']),
'action_selected':(page_id,),
'content':page.content,
'title':page.title})
@@ -537,7 +570,7 @@ def extraPage(request, area_name="", page_id=""):
return render_to_response(tpl, response_dct,
context_instance=RequestContext(request))
-def getDetail(request, area_name, marker_id):
+def getDetail(request, map_name, marker_id):
'''
Get the detail for a marker
'''
@@ -546,7 +579,7 @@ def getDetail(request, area_name, marker_id):
status__in=['A', 'S'])[0]
except (ValueError, IndexError):
return HttpResponse('no results')
- response_dct, redir = get_base_response(request, area_name)
+ response_dct, redir = get_base_response(request, map_name)
if redir:
return redir
response_dct['marker'] = marker
@@ -554,7 +587,7 @@ def getDetail(request, area_name, marker_id):
if 'simple' in request.GET and request.GET['simple']:
response_dct['simple'] = True
response_dct['share_networks'], net_dct = \
- getShareNetwork(request, response_dct['area_name'], marker)
+ getShareNetwork(request, response_dct['map_name'], marker)
response_dct['share_url'] = net_dct['url']
net_dct['to'] = settings.CONTACT_EMAIL
if net_dct['to']:
@@ -569,7 +602,7 @@ def getDetail(request, area_name, marker_id):
return render_to_response('chimere/detail.html', response_dct,
context_instance=RequestContext(request))
-def getDescriptionDetail(request, area_name, category_id):
+def getDescriptionDetail(request, map_name, category_id):
'''
Get the description for a category
'''
@@ -577,7 +610,7 @@ def getDescriptionDetail(request, area_name, category_id):
category = Category.objects.filter(id=int(category_id))[0]
except (ValueError, IndexError):
return HttpResponse('no results')
- response_dct, redir = get_base_response(request, area_name)
+ response_dct, redir = get_base_response(request, map_name)
if redir:
return redir
response_dct['category'] = category
@@ -599,7 +632,7 @@ def checkDate(q):
)
return q
-def getGeoObjects(request, area_name, category_ids, status):
+def getGeoObjects(request, map_name, category_ids, status):
'''
Get the JSON for markers and routes
'''
@@ -617,11 +650,17 @@ def getGeoObjects(request, area_name, category_ids, status):
idx = 0
current_cat = c_cat
colors = list(Color.objects.filter(color_theme = c_cat.color_theme))
+ color = '000'
if colors:
- jsons.append(json.loads(
- route.getGeoJSON(color=colors[idx % len(colors)].code)))
- else:
- jsons.append(json.loads(route.getGeoJSON(color='000')))
+ color = colors[idx % len(colors)].code
+ if '#' not in color:
+ color = '#' + color
+ base_dct = {"type":"Feature",
+ "properties":{
+ "color":color
+ }
+ }
+ jsons.append(route._getItems(base_dct))
idx += 1
try:
q = checkDate(Q(status__in=status, categories__in=category_ids))
@@ -629,30 +668,46 @@ def getGeoObjects(request, area_name, category_ids, status):
except:
return HttpResponse('no results')
category_ids = [int(cat_id) for cat_id in category_ids]
- for geo_object in list(query):
- jsons += json.loads(geo_object.getGeoJSON(category_ids))
+ for category_id in category_ids:
+ if not category_id:
+ continue
+ cat = SubCategory.objects.get(pk=category_id)
+ base_dct = {"type":"Feature",
+ "properties":{
+ "icon_path":unicode(cat.icon.image),
+ "icon_hover_path":cat.hover_icon.image \
+ if cat.hover_icon else '',
+ "icon_width":cat.icon.image.width,
+ 'icon_height':cat.icon.image.height,
+ 'category_name':cat.name}
+ }
+ for values in query.filter(categories__pk=category_id).values(
+ 'point', 'pk', 'name', 'weight'):
+ jsons.append(Marker._getJson(values, copy.deepcopy(base_dct)))
if not jsons:
return HttpResponse('no results')
- data = json.dumps({"type": "FeatureCollection", "features":jsons})
+ data = {"type": "FeatureCollection", "features":jsons}
+ data = json.dumps(data)
+
return HttpResponse(data, content_type="application/json")
-def getMarker(request, area_name, pk):
+def getMarker(request, map_name, pk):
q = Marker.objects.filter(pk=pk, status='A')
if not q.count():
return HttpResponse('{}')
data = q.all()[0].getGeoJSON()
return HttpResponse(data, content_type="application/json")
-def get_all_categories(request, area_name=None):
+def get_all_categories(request, map_name=None):
'''
Get all available categories in JSON
'''
- context_data, redir = get_base_response(request, area_name)
- area = context_data["area"]
+ context_data, redir = get_base_response(request, map_name)
+ map = context_data["map"]
subcategories = []
- if area:
- subcategories = list(area.getCategories('A',
- area_name=context_data['area_name']))
+ if map:
+ subcategories = list(map.getCategories('A',
+ map_name=context_data['map_name']))
else:
categories = SubCategory.getAvailable()
for cat, subcats in categories:
@@ -661,25 +716,25 @@ def get_all_categories(request, area_name=None):
jsons = json.dumps({'categories':subcats})
return HttpResponse(jsons)
-def get_available_categories(request, area_name=None, area=None, status='A',
+def get_available_categories(request, map_name=None, map=None, status='A',
force=None):
'''
- Get category menu for a designed area
+ Get category menu for a designed map
'''
- context_data, redir = get_base_response(request, area_name)
- area = context_data["area"]
+ context_data, redir = get_base_response(request, map_name)
+ map = context_data["map"]
if redir:
return redir
- if area and area.dynamic_categories and \
+ if map and map.dynamic_categories and \
not "current_extent" in request.GET:
context_data['sub_categories'] = []
return render_to_response('chimere/blocks/categories.html', context_data,
context_instance=RequestContext(request))
- if not area or not area.dynamic_categories:
+ if not map or not map.dynamic_categories:
# Categories are not updated dynamicaly when the user move the map
# so we return ALL the categories
subcategories = SubCategory.getAvailable(
- area_name=context_data['area_name'])
+ map_name=context_data['map_name'])
context_data['sub_categories'] = subcategories
return render_to_response('chimere/blocks/categories.html', context_data,
context_instance=RequestContext(request))
@@ -691,12 +746,12 @@ def get_available_categories(request, area_name=None, area=None, status='A',
status = status.split('_')
current_extent = request.GET["current_extent"].replace('M', '-')\
.replace('D', '.')
- area = SimpleArea([float(pt) for pt in current_extent.split('_')])
+ map = SimpleArea([float(pt) for pt in current_extent.split('_')])
except:
# bad extent format
return HttpResponse(default_message)
- # if not force and area.isIn(SimpleArea(cookie.AREA):return
- categories = area.getCategories(status, area_name=context_data['area_name'])
+ # if not force and map.isIn(SimpleArea(cookie.AREA):return
+ categories = map.getCategories(status, map_name=context_data['map_name'])
if not categories:
return HttpResponse(default_message)
get_cat = lambda subcat: subcat.category
@@ -709,7 +764,7 @@ def get_available_categories(request, area_name=None, area=None, status='A',
return render_to_response('chimere/blocks/categories.html', context_data,
context_instance=RequestContext(request))
-def getCategory(request, area_name='', category_id=0):
+def getCategory(request, map_name='', category_id=0):
'''
Get the JSON for a category (mainly in order to get the description)
'''
@@ -719,7 +774,7 @@ def getCategory(request, area_name='', category_id=0):
return HttpResponse('no results')
return HttpResponse(category.getJSON())
-def getTinyfiedUrl(request, parameters, area_name=''):
+def getTinyfiedUrl(request, parameters, map_name=''):
'''
Get the tinyfied version of parameters
'''
@@ -728,11 +783,11 @@ def getTinyfiedUrl(request, parameters, area_name=''):
urn = TinyUrl.getUrnByParameters(parameters)
except:
return {}
- response_dct, redir = get_base_response(request, area_name)
+ response_dct, redir = get_base_response(request, map_name)
if redir:
return redir
- url = reverse('chimere:tiny', args=[(response_dct['area_name'] \
- if response_dct['area_name'] else '') + '/', urn])
+ url = reverse('chimere:tiny', args=[(response_dct['map_name'] \
+ if response_dct['map_name'] else '') + '/', urn])
if not url.startswith('http'):
url = get_base_uri(request) + url
url = re.sub("([^:])\/\/", "\g<1>/", url)
@@ -749,17 +804,17 @@ def getTinyfiedUrl(request, parameters, area_name=''):
data["text"] = urlquote(text)
return data
-def redirectFromTinyURN(request, area_name='', tiny_urn=''):
+def redirectFromTinyURN(request, map_name='', tiny_urn=''):
"""
Redirect from a tiny Urn
"""
parameters = '?' + TinyUrl.getParametersByUrn(tiny_urn)
- response_dct, redir = get_base_response(request, area_name)
+ response_dct, redir = get_base_response(request, map_name)
if redir:
return redir
return HttpResponseRedirect(response_dct['extra_url'] + parameters)
-def route(request, area_name, lon1, lat1, lonlat_steps, lon2, lat2,
+def route(request, map_name, lon1, lat1, lonlat_steps, lon2, lat2,
transport='foot', speed=''):
'''
Get the JSON for a route
@@ -838,14 +893,18 @@ def route(request, area_name, lon1, lat1, lonlat_steps, lon2, lat2,
message)
return HttpResponse(data)
-def rss(request, area_name=''):
+def get_json(request, map_name='', app_name='', filename=''):
+ return HttpResponse(open(settings.STATIC_ROOT+app_name+'/json/'+filename+'.json'),
+ 'application/javascript', status=200)
+
+def rss(request, map_name='', actions=default_actions):
'''
Redirect to RSS subscription page
'''
- response_dct, redir = get_base_response(request, area_name)
+ response_dct, redir = get_base_response(request, map_name)
if redir:
return redir
- response_dct.update({'actions':actions(response_dct['area_name']),
+ response_dct.update({'actions':actions(request.user, response_dct['map_name']),
'action_selected':('rss',),
'category_rss_feed':'',})
# If the form has been submited
@@ -867,14 +926,14 @@ def rss(request, area_name=''):
# User wants to follow all the new POI situated in a defined area
elif request.POST['rss_category'] == 'area':
# An unbound form
- form = AreaForm()
+ form = MapForm()
area_widget = AreaWidget().render('area', None)
response_dct.update({
'map_layer':settings.CHIMERE_DEFAULT_MAP_LAYER,
'extra_head':form.media,
'form':form,
'category_rss_feed':'area',
- 'area_id':Area.getAvailable(),
+ 'map_id':Map.getAvailable(),
'area_widget':area_widget
})
return render_to_response('chimere/feeds/rss.html',
@@ -905,10 +964,10 @@ def rss(request, area_name=''):
kwargs={'category_id':cat_id})
return redirect(feeds_link)
- # User has specified the ID of the area he wants to follow
- if 'id_area' in request.POST and request.POST['id_area'] != '':
- feeds_link = reverse('chimere:feeds-areaid',
- kwargs={'area_id':request.POST['id_area']})
+ # User has specified the ID of the map he wants to follow
+ if 'id_map' in request.POST and request.POST['id_map'] != '':
+ feeds_link = reverse('chimere:feeds-mapid',
+ kwargs={'map_id':request.POST['id_map']})
return redirect(feeds_link)
# User has specified the area he wants to follow => we redirect him
@@ -943,12 +1002,12 @@ def rss(request, area_name=''):
context_instance=RequestContext(request))
if request.GET['rss_category'] == 'area':
# An unbound form
- form = AreaForm()
+ form = MapForm()
response_dct.update({'map_layer':settings.MAP_LAYER,
'extra_head':form.media,
'form':form,
'category_rss_feed':'area',
- 'area_id':Area.getAvailable(),
+ 'map_id':Map.getAvailable(),
'area_widget':AreaWidget().render('area', None)})
return render_to_response('chimere/feeds/rss.html', response_dct,
context_instance=RequestContext(request))
diff --git a/chimere/widgets.py b/chimere/widgets.py
index e7d104a..e357268 100644
--- a/chimere/widgets.py
+++ b/chimere/widgets.py
@@ -36,7 +36,7 @@ from django.template.loader import render_to_string
import re
-def getMapJS(area_name=''):
+def getMapJS(map_name=''):
'''Variable initialization for drawing the map
'''
# projection, center and bounds definitions
@@ -53,30 +53,30 @@ def getMapJS(area_name=''):
js += u"var map_layer = %s;\n" % settings.CHIMERE_DEFAULT_MAP_LAYER
js += u"var restricted_extent;\n"
- if area_name:
- js += u"var area_name='%s';\n" % area_name
+ if map_name:
+ js += u"var map_name='%s';\n" % map_name
js = u"<script type='text/javascript'><!--\n"\
u"%s// !--></script>\n" % js
return js
-def get_map_layers(area_name=''):
- from chimere.models import Area
- area = None
- if area_name:
+def get_map_layers(map_name='', force_default=False):
+ from chimere.models import Map
+ map = None
+ if map_name:
try:
- area = Area.objects.get(urn=area_name)
+ map = Map.objects.get(urn=map_name)
except ObjectDoesNotExist:
pass
else:
try:
- area = Area.objects.get(default=True)
+ map = Map.objects.get(default=True)
except ObjectDoesNotExist:
pass
map_layers, default = [], None
- if area and area.layers.count():
+ if not force_default and map and map.layers.count():
map_layers = [[layer.name, layer.layer_code, False]
- for layer in area.layers.order_by('arealayers__order').all()]
- def_layer = area.layers.filter(arealayers__default=True)
+ for layer in map.layers.order_by('maplayers__order').all()]
+ def_layer = map.layers.filter(maplayers__default=True)
if def_layer.count():
def_layer = def_layer.all()[0]
for order, map_layer in enumerate(map_layers):
@@ -85,7 +85,7 @@ def get_map_layers(area_name=''):
map_layers[order][2] = True
else:
map_layers[0][2] = True
- elif settings.CHIMERE_DEFAULT_MAP_LAYER:
+ elif not force_default and settings.CHIMERE_DEFAULT_MAP_LAYER:
map_layers = [(_(u"Default layer"), settings.CHIMERE_DEFAULT_MAP_LAYER,
True)]
else:
@@ -225,23 +225,11 @@ class DatePickerWidget(forms.TextInput):
class NominatimWidget(forms.TextInput):
class Media:
js = ["%schimere/js/nominatim-widget.js" % settings.STATIC_URL]
- def render(self, name, value, attrs=None, area_name=''):
- tpl = u"""
-<input type='hidden' name='nominatim_%(id)s_lat' id='nominatim_%(id)s_lat'/>
-<input type='hidden' name='nominatim_%(id)s_lon' id='nominatim_%(id)s_lon'/>
-<input type='text' class='nominatim-widget' name='nominatim_%(id)s' id='nominatim_%(id)s' value=""/>
-<label class='nominatim-label' id='nominatim_%(id)s_label'>&nbsp;</label>
-<script type='text/javascript'>
-var default_nominatim_lbl = "%(label)s";
-var nominatim_url = "%(nominatim_url)s";
-$("#nominatim_%(id)s").val(default_nominatim_lbl);
-$("#nominatim_%(id)s").click(function(){
- $("#nominatim_%(id)s").val('');
-});
-</script>
-""" % {'id':name, 'nominatim_url':settings.NOMINATIM_URL,
- 'label':_(u"Street, City, Country")}
- return mark_safe(tpl)
+ def render(self, name, value=None, attrs=None, map_name=''):
+ return mark_safe(
+ render_to_string('chimere/blocks/nominatim_widget.html',
+ {'id':name, 'nominatim_url':settings.NOMINATIM_URL}
+ ))
class PointChooserWidget(forms.TextInput):
"""
@@ -249,11 +237,11 @@ class PointChooserWidget(forms.TextInput):
"""
class Media:
css = {
- "all": settings.OSM_CSS_URLS + \
+ "all": settings.MAP_CSS_URLS[settings.CHIMERE_EDIT_RENDERER] + \
["%schimere/css/forms.css" % settings.STATIC_URL,]
}
- js = settings.OSM_JS_URLS + list(settings.JQUERY_JS_URLS) + \
- ["%schimere/js/jquery.chimere.js" % settings.STATIC_URL]
+ js = list(settings.JQUERY_JS_URLS) + \
+ settings.MAP_JS_URLS[settings.CHIMERE_EDIT_RENDERER]
def render(self, name, value, attrs=None, area_name='', initialized=True):
'''
@@ -273,9 +261,12 @@ class PointChooserWidget(forms.TextInput):
value = None
else:
value = None
- map_layers, default_area = get_map_layers(area_name)
+ force = False
+ if settings.CHIMERE_EDIT_RENDERER != settings.CHIMERE_VIEW_RENDERER:
+ force = True
+ map_layers, default_map = get_map_layers(map_name, force_default=force)
map_layers = [js for n, js, default in map_layers]
- #TODO: manage area
+ #TODO: manage maps
return mark_safe(
render_to_string('chimere/blocks/live_coordinates.html',
{'lat': _("Latitude"),
@@ -286,7 +277,7 @@ class PointChooserWidget(forms.TextInput):
'val': val,
'initialized': initialized,
'isvalue': bool(value),
- 'default_area': "true" if default_area else "false",
+ 'default_map': "true" if default_map else "false",
}) % \
(settings.STATIC_URL,
settings.CHIMERE_EPSG_DISPLAY_PROJECTION,
@@ -331,20 +322,21 @@ class RouteChooserWidget(forms.TextInput):
Manage the edition of route on a map
"""
class Media:
- css = {"all": settings.OSM_CSS_URLS + \
- ["%schimere/css/forms.css" % settings.STATIC_URL,]
+ css = {
+ "all": settings.MAP_CSS_URLS[settings.CHIMERE_EDIT_RENDERER] + \
+ ["%schimere/css/forms.css" % settings.STATIC_URL,]
}
- js = settings.OSM_JS_URLS + list(settings.JQUERY_JS_URLS) + \
- ["%schimere/js/jquery.chimere.js" % settings.STATIC_URL,
- "%schimere/js/edit_route_map.js" % settings.STATIC_URL,
+ js = list(settings.JQUERY_JS_URLS) + \
+ settings.MAP_JS_URLS[settings.CHIMERE_EDIT_RENDERER] + \
+ ["%schimere/js/edit_route_map.js" % settings.STATIC_URL,
"%schimere/js/base.js" % settings.STATIC_URL,]
- def render(self, name, value, attrs=None, area_name='', routefile_id=None):
+ def render(self, name, value, attrs=None, map_name='', routefile_id=None):
'''
Render a map and latitude, longitude information field
'''
- tpl = getMapJS(area_name)
- map_layers, default_area = get_map_layers(area_name)
+ tpl = getMapJS(map_name)
+ map_layers, default_map = get_map_layers(map_name)
map_layers = [js for nm, js, default in map_layers]
js = """
var resolutions;
@@ -366,12 +358,12 @@ class RouteChooserWidget(forms.TextInput):
settings.CHIMERE_EPSG_DISPLAY_PROJECTION,
settings.CHIMERE_EPSG_PROJECTION, settings.CHIMERE_DEFAULT_CENTER,
settings.CHIMERE_DEFAULT_ZOOM, ", ".join(map_layers))
- if default_area:
+ if default_map:
js += "chimere_init_options['selected_map_layer'] = %d;\n" % \
- default_area
+ default_map
tpl = u"<script type='text/javascript'><!--\n"\
u"%s// !--></script>\n" % js
- #TODO: manage area
+ #TODO: manage maps
help_create = ''
if not value:
help_create = u"<h3>%s</h3>\n"\
@@ -464,15 +456,15 @@ class RouteField(models.LineStringField):
class AreaWidget(forms.TextInput):
"""
- Manage the edition of an area on the map
+ Manage the edition of an areaon the map
"""
class Media:
css = {
- "all": settings.OSM_CSS_URLS + \
+ "all": settings.MAP_CSS_URLS[settings.CHIMERE_EDIT_RENDERER] + \
["%schimere/css/forms.css" % settings.STATIC_URL,]
}
- js = settings.OSM_JS_URLS + [
- "%schimere/js/edit_area.js" % settings.STATIC_URL,
+ js = settings.MAP_JS_URLS[settings.CHIMERE_EDIT_RENDERER] + \
+ ["%schimere/js/edit_area.js" % settings.STATIC_URL,
"%schimere/js/base.js" % settings.STATIC_URL,]
def get_bounding_box_from_value(self, value):
@@ -567,11 +559,11 @@ class ImportFiltrWidget(AreaWidget):
"""
class Media:
css = {
- "all": settings.OSM_CSS_URLS + \
+ "all": settings.MAP_CSS_URLS[settings.CHIMERE_EDIT_RENDERER] + \
["%schimere/css/forms.css" % settings.STATIC_URL,]
}
- js = settings.OSM_JS_URLS + [
- "%schimere/js/edit_area.js" % settings.STATIC_URL,
+ js = settings.MAP_JS_URLS[settings.CHIMERE_EDIT_RENDERER] + \
+ ["%schimere/js/edit_area.js" % settings.STATIC_URL,
"%schimere/js/base.js" % settings.STATIC_URL,]
def render(self, name, value, attrs=None):