diff options
76 files changed, 12398 insertions, 1292 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 f0b15b1..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: 2013-03-14 18:43+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:274 +#: admin.py:216 admin.py:292 msgid "Submitter" msgstr "Demandeur" -#: admin.py:202 admin.py:279 admin.py:331 +#: admin.py:221 admin.py:297 admin.py:357 msgid "Import" msgstr "Import" -#: admin.py:207 admin.py:284 +#: admin.py:226 admin.py:302 msgid "Associated items" msgstr "Éléments associés" -#: admin.py:337 +#: admin.py:363 msgid "Cancel import" msgstr "Annuler l'import" -#: admin.py:343 +#: admin.py:369 msgid "Cancel export" msgstr "Annuler l'export" -#: admin.py:347 +#: 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:352 +#: 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:356 +#: 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:363 +#: 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:375 +#: admin.py:401 msgid "Export launched." msgstr "Export lancé." -#: admin.py:379 +#: 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:384 +#: admin.py:410 msgid "Export to osm" msgstr "Exporter vers osm" @@ -166,7 +170,7 @@ msgstr "Objet" msgid "OSM user" msgstr "Utilisateur OSM" -#: forms.py:118 models.py:1500 +#: forms.py:118 models.py:1635 msgid "Password" msgstr "Mot de passe" @@ -219,46 +223,46 @@ msgstr "La date de fin ne peut pas être antérieure à la date de début" msgid "This field is mandatory for the selected categories" msgstr "Ce champ est obligatoire pour les catégories sélectionnées" -#: forms.py:501 +#: forms.py:507 msgid "File" msgstr "Fichier" -#: forms.py:507 +#: forms.py:513 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:512 models.py:53 models.py:101 models.py:163 models.py:184 -#: models.py:197 models.py:212 models.py:375 models.py:720 models.py:776 -#: models.py:835 models.py:953 models.py:1304 models.py:1316 models.py:1490 -#: utils.py:485 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:521 models.py:1353 +#: forms.py:527 msgid "Area" msgstr "Zone" -#: forms.py:561 +#: forms.py:567 msgid "No area selected." msgstr "Pas de zone sélectionnée." -#: forms.py:568 +#: forms.py:574 #, python-format msgid "The area \"%s\" has the same order, you need to choose another one." msgstr "" "La zone « %s » a le même numéro d'ordre, vous devez un choisir un autre." -#: forms.py:613 +#: forms.py:625 msgid "Start" msgstr "Départ" -#: forms.py:614 +#: forms.py:626 msgid "Finish" msgstr "Arrivée" -#: forms.py:615 +#: forms.py:627 msgid "Speed" msgstr "Vitesse" @@ -266,13 +270,13 @@ msgstr "Vitesse" msgid "Mnemonic" msgstr "Mnémonique" -#: models.py:56 models.py:102 models.py:185 models.py:213 models.py:368 -#: models.py:724 models.py:1322 models.py:1492 models.py:1533 +#: 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:57 models.py:173 models.py:186 models.py:230 models.py:778 -#: models.py:850 models.py:1321 models.py:1479 models.py:1491 +#: 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" @@ -284,21 +288,21 @@ msgstr "Chemin du patron" msgid "Page" msgstr "Page" -#: models.py:103 models.py:469 +#: models.py:103 models.py:474 msgid "Is front page" msgstr "Est en page principale" -#: models.py:105 models.py:1501 +#: models.py:105 models.py:1636 msgid "Date" msgstr "Date" -#: models.py:107 models.py:777 +#: models.py:107 models.py:809 msgid "Url" msgstr "Url" #: models.py:108 -msgid "Associated areas" -msgstr "Zones associées" +msgid "Associated maps" +msgstr "Cartes associées" #: models.py:114 models.py:115 templates/chimere/blocks/news.html:3 #: templates/chimere/blocks/news.html:5 @@ -313,7 +317,7 @@ msgstr "Paramètres" msgid "TinyUrl" msgstr "Mini-url" -#: models.py:167 models.py:174 models.py:225 +#: models.py:167 models.py:174 models.py:228 msgid "Color theme" msgstr "Thème de couleur" @@ -321,401 +325,409 @@ msgstr "Thème de couleur" msgid "Code" msgstr "Code" -#: models.py:179 +#: models.py:180 msgid "Color" msgstr "Couleur" -#: models.py:192 models.py:210 +#: models.py:193 models.py:211 msgid "Category" msgstr "Catégorie" -#: models.py:198 models.py:716 models.py:836 models.py:1019 +#: 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:200 models.py:838 models.py:1021 +#: models.py:201 models.py:870 models.py:1053 msgid "Height" msgstr "Hauteur" -#: models.py:201 models.py:839 models.py:1022 +#: models.py:202 models.py:871 models.py:1054 msgid "Width" msgstr "Largeur" -#: models.py:205 models.py:222 +#: models.py:206 models.py:225 msgid "Icon" msgstr "Icône" -#: models.py:214 +#: models.py:215 msgid "Available for submission" msgstr "Disponible pour soumission" -#: models.py:216 +#: 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:217 models.py:1015 models.py:1032 -#: 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:218 +#: models.py:221 msgid "Both" msgstr "Mixte" -#: models.py:219 +#: models.py:222 msgid "Item type" msgstr "Type d'élément" -#: models.py:220 +#: models.py:223 msgid "Is dated" msgstr "Est daté" -#: models.py:223 +#: models.py:226 msgid "Hover icon" msgstr "Icône en survol" -#: models.py:227 +#: models.py:230 msgid "Displayed in the layer menu" msgstr "Apparaît dans le menu des couches ?" -#: models.py:229 +#: models.py:232 msgid "Routing warn" msgstr "Avertissement sur les itinéraires" -#: models.py:235 +#: models.py:238 msgid "Sub-category" msgstr "Sous-catégorie" -#: models.py:236 +#: models.py:239 msgid "Sub-categories" msgstr "Sous-catégories" -#: models.py:320 +#: models.py:323 msgid "Importer type" msgstr "Type d'import" -#: models.py:322 +#: models.py:325 msgid "Filter" msgstr "Filtre" -#: models.py:324 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:326 +#: models.py:329 msgid "Source file" msgstr "Fichier source" -#: models.py:328 +#: models.py:331 msgid "Name by default" msgstr "Nom par défaut" -#: models.py:330 +#: models.py:333 msgid "SRID" msgstr "SRID" -#: models.py:331 +#: models.py:334 msgid "Zipped file" msgstr "Fichier zippé" -#: models.py:332 +#: models.py:335 msgid "Overwrite existing data" msgstr "Écraser les données existantes" -#: models.py:334 +#: models.py:337 msgid "Get description from source" msgstr "Obtenir une description depuis la source" -#: models.py:336 +#: models.py:339 msgid "Default description" msgstr "Description par défaut" -#: models.py:338 models.py:396 +#: models.py:341 models.py:399 msgid "Origin" msgstr "Origine" -#: models.py:340 models.py:398 +#: models.py:343 models.py:401 msgid "License" msgstr "Licence" -#: models.py:343 +#: models.py:346 msgid "Associated subcategories" msgstr "Sous-catégories associées" -#: models.py:344 utils.py:489 +#: models.py:347 utils.py:491 msgid "State" msgstr "État" -#: models.py:346 +#: models.py:349 msgid "Automatically associate a marker to a way" msgstr "Associer automatiquement un marqueur à une route" -#: models.py:350 +#: models.py:353 msgid "Importer" msgstr "Import" -#: models.py:367 +#: models.py:370 msgid "Submited" msgstr "Soumis" -#: models.py:369 +#: models.py:372 msgid "Modified" msgstr "Modifié" -#: models.py:370 +#: models.py:373 msgid "Disabled" msgstr "Désactivé" -#: models.py:371 +#: models.py:374 msgid "Imported" msgstr "Importé" -#: models.py:377 +#: models.py:380 msgid "Submitter session key" msgstr "Clé de session du demandeur" -#: models.py:379 +#: models.py:382 msgid "Submitter name or nickname" msgstr "Nom ou pseudo du demandeur" -#: models.py:381 +#: models.py:384 msgid "Submitter email" msgstr "Courriel du demandeur" -#: models.py:383 +#: models.py:386 msgid "Submitter comment" msgstr "Commentaire du demandeur" -#: models.py:385 models.py:1195 +#: models.py:388 models.py:1234 msgid "Status" msgstr "État" -#: models.py:386 +#: models.py:389 msgid "Import key" msgstr "Clé d'import" -#: models.py:388 +#: models.py:391 msgid "Import version" msgstr "Version de l'import" -#: models.py:390 +#: models.py:393 msgid "Source" msgstr "Source" -#: models.py:392 +#: models.py:395 msgid "Modified since last import" msgstr "Modifié depuis le dernier import" -#: models.py:394 +#: models.py:397 msgid "Not to be exported to OSM" msgstr "À ne pas exporter vers OSM" -#: models.py:400 templates/chimere/edit.html:57 -#: 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:401 +#: 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:403 templates/chimere/edit.html:63 -#: 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:404 +#: 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:461 +#: models.py:464 msgid "Reference marker" msgstr "Point d'intérêt de référence" -#: models.py:462 utils.py:491 +#: models.py:465 utils.py:493 msgid "Localisation" msgstr "Localisation" -#: models.py:464 +#: models.py:467 msgid "Available Date" msgstr "Date de mise en disponibilité" -#: models.py:468 utils.py:490 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:539 models.py:1543 +#: models.py:548 models.py:1683 msgid "Point of interest" msgstr "Point d'intérêt" -#: models.py:714 +#: models.py:746 msgid "Audio" msgstr "Audio" -#: models.py:715 +#: models.py:747 msgid "Video" msgstr "Vidéo" -#: models.py:717 +#: models.py:749 msgid "Other" msgstr "Autre" -#: models.py:718 +#: models.py:750 msgid "Media type" msgstr "Type de media" -#: models.py:721 +#: models.py:753 msgid "Mime type" msgstr "Type mime" -#: models.py:723 +#: models.py:755 msgid "Inside an iframe" msgstr "À l'intérieur d'un iframe" -#: models.py:727 +#: models.py:759 msgid "Multimedia type" msgstr "Type de multimedia" -#: models.py:728 +#: models.py:760 msgid "Multimedia types" msgstr "Types de multimedia" -#: models.py:737 +#: models.py:769 msgid "Automatic recognition" msgstr "Reconnaissance automatique" -#: models.py:763 +#: models.py:795 msgid "Extension name" msgstr "Nom de l'extension" -#: models.py:765 +#: models.py:797 msgid "Associated multimedia type" msgstr "Type de multimedia associé" -#: models.py:769 +#: models.py:801 msgid "Multimedia extension" msgstr "Extension multimedia" -#: models.py:770 +#: models.py:802 msgid "Multimedia extensions" msgstr "Extensions multimedia" -#: models.py:780 models.py:840 +#: models.py:812 models.py:872 msgid "Display inside the description?" msgstr "Apparaît dans la description ?" -#: models.py:785 +#: models.py:817 msgid "Multimedia file" msgstr "Fichier multimedia" -#: models.py:786 +#: models.py:818 msgid "Multimedia files" msgstr "Fichiers multimedias" -#: models.py:842 +#: models.py:874 msgid "Thumbnail" msgstr "Miniature" -#: models.py:846 +#: models.py:878 msgid "Thumbnail height" msgstr "Hauteur de la miniature" -#: models.py:848 +#: models.py:880 msgid "Thumbnail width" msgstr "Largeur de la miniature" -#: models.py:857 +#: models.py:889 msgid "Picture file" msgstr "Fichier d'image" -#: models.py:858 +#: models.py:890 msgid "Picture files" msgstr "Fichiers d'image" -#: models.py:954 +#: models.py:986 msgid "Raw file (gpx or kml)" msgstr "Fichier brut (gpx ou kml)" -#: models.py:956 +#: models.py:988 msgid "Simplified file" msgstr "Fichier simplifié" -#: models.py:958 +#: models.py:990 msgid "KML" msgstr "KML" -#: models.py:958 +#: models.py:990 msgid "GPX" msgstr "GPX" -#: models.py:963 +#: models.py:995 msgid "Route file" msgstr "Fichier de trajet" -#: models.py:964 +#: models.py:996 msgid "Route files" msgstr "Fichiers de trajet" -#: models.py:1014 +#: models.py:1046 msgid "Reference route" msgstr "Trajet de référence" -#: models.py:1018 +#: models.py:1050 msgid "Associated file" msgstr "Fichier associé" -#: models.py:1023 +#: models.py:1055 msgid "Has an associated marker" msgstr "Dispose d'un marqueur associé" -#: models.py:1305 +#: models.py:1344 msgid "Layer code" msgstr "Code pour la couche" -#: models.py:1311 +#: models.py:1350 msgid "Layer" msgstr "Couche" -#: models.py:1317 -msgid "Area urn" -msgstr "Urn de la zone" +#: models.py:1358 +msgid "Map urn" +msgstr "Urn de la carte" -#: models.py:1319 templates/chimere/blocks/welcome.html:3 +#: models.py:1360 templates/chimere/blocks/welcome.html:3 msgid "Welcome message" msgstr "Message d'accueil" -#: models.py:1323 +#: models.py:1363 msgid "Upper left corner" msgstr "Coin en haut à gauche" -#: models.py:1325 +#: models.py:1365 msgid "Lower right corner" msgstr "Coin en bas à droite" -#: models.py:1327 -msgid "Default area" -msgstr "Zone par défaut" +#: models.py:1367 +msgid "Default map" +msgstr "Carte par défaut" -#: models.py:1328 -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:1332 +#: models.py:1372 msgid "Sub-categories checked by default" msgstr "Sous-catégories cochées par défaut" -#: models.py:1334 +#: models.py:1374 msgid "Sub-categories dynamicaly displayed" msgstr "Sous-categories affichées dynamiquement" -#: models.py:1335 +#: models.py:1375 msgid "" "If checked, categories are only displayed in the menu if they are available " "on the current extent." @@ -723,75 +735,147 @@ msgstr "" "Si coché, les catégories sont disponibles sur le menu seulement si elles " "apparaissent sur la zone affichée." -#: models.py:1339 models.py:1495 +#: models.py:1379 models.py:1630 msgid "Restricted to theses sub-categories" msgstr "Restreindre à ces sous-categories" -#: models.py:1340 +#: 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:1342 +#: models.py:1382 msgid "Link to an external CSS" msgstr "Lien vers une feuille de style externe" -#: models.py:1344 +#: 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:1480 widgets.py:88 +#: 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:1484 models.py:1485 +#: models.py:1618 models.py:1619 msgid "Layers" msgstr "Couches" -#: models.py:1493 +#: models.py:1627 msgid "Mandatory" msgstr "Obligatoire" -#: models.py:1496 +#: 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:1498 +#: models.py:1633 msgid "Text" msgstr "Texte" -#: models.py:1499 +#: models.py:1634 msgid "Long text" msgstr "Texte long" -#: models.py:1502 +#: models.py:1637 msgid "Choices" msgstr "Choix" -#: models.py:1510 +#: models.py:1645 msgid "Type" msgstr "Type" -#: models.py:1515 models.py:1531 models.py:1545 +#: models.py:1650 models.py:1671 models.py:1685 msgid "Property model" msgstr "Modèle de propriété" -#: models.py:1532 models.py:1546 +#: models.py:1672 models.py:1686 msgid "Value" msgstr "Valeur" -#: models.py:1538 +#: models.py:1678 msgid "Model property choice" msgstr "Choix pour les modèles de propriété" -#: models.py:1557 +#: models.py:1701 msgid "Property" msgstr "Propriété" +#: settings.sample.py:86 +msgid "Foot" +msgstr "À pied" + +#: settings.sample.py:87 +msgid "Bicycle" +msgstr "À vélo" + +#: settings.sample.py:88 +msgid "Motorcar" +msgstr "En voiture" + +#: settings.sample.py:91 +msgid "You are walking slowly" +msgstr "Vous marchez lentement" + +#: settings.sample.py:92 +msgid "You are walking pretty quickly" +msgstr "Vous marchez assez rapidement" + +#: settings.sample.py:93 +msgid "You are riding pretty slowly" +msgstr "Vous roulez plutôt lentement" + +#: settings.sample.py:94 +msgid "You are riding pretty quickly" +msgstr "Vous roulez plutôt rapidement" + #: tasks.py:63 msgid "Import pending" msgstr "Import en attente" @@ -842,70 +926,73 @@ msgstr "Export échoué" msgid "Export canceled" msgstr "Export annulé" -#: utils.py:148 utils.py:197 +#: utils.py:150 utils.py:199 msgid "Bad zip file" msgstr "Mauvais fichier zip" -#: utils.py:200 +#: utils.py:202 msgid "Missing file(s) inside the zip file" msgstr "Fichier(s) manquant(s) dans l'archive zip" -#: utils.py:241 +#: utils.py:243 msgid "Bad XML file" msgstr "Mauvais fichier XML" -#: utils.py:328 +#: utils.py:330 msgid "Error while reading the data source." msgstr "Erreur lors de la lecture de la source." -#: utils.py:346 +#: 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:367 +#: utils.py:369 #, python-format -msgid "Type of geographic item (%s) of this shapefile is not managed by Chimère." +msgid "" +"Type of geographic item (%s) of this shapefile is not managed by Chimère." msgstr "" -"Les types des éléments géographiques (%s) de ce fichier Shapefile ne sont pas " - "gérés par Chimère." +"Les types des éléments géographiques (%s) de ce fichier Shapefile ne sont " +"pas gérés par Chimère." -#: utils.py:387 +#: utils.py:389 msgid "Bad Shapefile" msgstr "Mauvais fichier Shapefile" -#: utils.py:429 +#: utils.py:431 msgid "Could not create file!" msgstr "Ne peut pas créer le fichier !" -#: utils.py:440 +#: utils.py:442 msgid "Failed to create field" msgstr "Ne peut pas créer un champ" -#: utils.py:486 templates/admin/chimere/managed_modified.html:25 -#: templates/chimere/edit.html:45 templates/chimere/edit_route.html:42 -#: templates/chimere/main_map.html:13 +#: 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:519 -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:597 +#: utils.py:630 msgid "RSS feed is not well formed" msgstr "Flux RSS non valide" -#: utils.py:673 +#: utils.py:706 msgid "Nothing to import" msgstr "Rien à importer" -#: utils.py:757 +#: 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:759 +#: utils.py:792 msgid "" "There are items from a former import not yet validated - validate them " "before exporting" @@ -913,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:771 +#: utils.py:804 msgid "Bad params - programming error" msgstr "Mauvais paramètres - erreur de programmation" -#: utils.py:781 +#: utils.py:814 msgid "Bad param" msgstr "Mauvais paramètre" -#: utils.py:796 +#: 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:798 +#: 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" @@ -934,19 +1021,23 @@ msgstr "" "vouloir lancer la requête sur la planète entière fixez la « bounding box » " "à -180,-90,180,90" -#: views.py:290 +#: views.py:161 +msgid "Invalid user or password." +msgstr "Nom d'utilisateur ou mot de passe incorrect" + +#: 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:375 +#: 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:487 +#: views.py:521 msgid "Comments/request on the map" msgstr "Commentaires/requètes sur la carte" -#: views.py:490 +#: 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." @@ -955,56 +1046,52 @@ msgstr "" "laissé votre courriel vous serez peut-être contacté bientôt pour plus de " "détails." -#: views.py:494 +#: views.py:528 msgid "Temporary error. Renew your message later." msgstr "Erreur temporaire. Réenvoyez votre message plus tard." -#: views.py:663 +#: views.py:722 msgid "No category available in this area." msgstr "Pas de catégorie disponible sur cette zone." -#: views.py:763 +#: views.py:822 msgid "Bad geometry" msgstr "Géométrie incorrecte" -#: views.py:848 +#: views.py:911 msgid "Incorrect choice in the list" msgstr "Choix incorrect dans la liste" -#: widgets.py:242 -msgid "Street, City, Country" -msgstr "Rue, Commune, Pays" - -#: widgets.py:311 +#: widgets.py:272 msgid "Latitude" msgstr "Latitude" -#: widgets.py:311 +#: widgets.py:274 msgid "Longitude" msgstr "Longitude" -#: widgets.py:335 +#: widgets.py:302 msgid "Invalid point" msgstr "Point invalide" -#: widgets.py:391 +#: widgets.py:359 msgid "Creation mode" msgstr "Mode création" -#: widgets.py:392 +#: widgets.py:360 msgid "To start drawing the route click on the toggle button: \"Draw\"." msgstr "" "Pour commencer le dessin cliquez sur le bouton : « Tracer »." -#: widgets.py:394 +#: 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:395 +#: widgets.py:363 msgid "You can add points by clicking again." msgstr "Vous pouvez ajouter des points en cliquant de nouveau." -#: widgets.py:396 +#: widgets.py:364 msgid "" "To finish the drawing double click. When the drawing is finished you can " "edit it." @@ -1012,7 +1099,7 @@ msgstr "" "Pour finir le tracé double-cliquez. Quand le tracé est fini vous pouvez " "toujours l'éditer." -#: widgets.py:398 +#: widgets.py:366 msgid "" "While creating to undo a drawing click again on the toggle button \"Stop " "drawing\"." @@ -1020,17 +1107,17 @@ msgstr "" "En mode création vous pouvez annuler un tracé en appuyant sur le bouton " "« Arrêter le tracé »." -#: widgets.py:403 +#: widgets.py:371 msgid "Modification mode" msgstr "Mode modification" -#: widgets.py:404 +#: 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:405 +#: widgets.py:373 msgid "" "To delete a point move the mouse cursor over it and press the \"d\" or \"Del" "\" key." @@ -1038,7 +1125,7 @@ msgstr "" "Pour supprimer un point, mettez le curseur de la souris sur celui-ci et " "appuyez sur le touche « d » ou « Suppr »." -#: widgets.py:407 +#: widgets.py:375 msgid "" "To add a point click in the middle of a segment and drag the new point to " "the desired position" @@ -1047,51 +1134,51 @@ msgstr "" "maintenez le bouton appuyé et déplacez le nouveau point à la position " "désirée." -#: widgets.py:414 +#: 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:417 +#: widgets.py:385 msgid "Upload a route file (GPX or KML)" msgstr "Déposer un trajet (fichier GPX ou KML)" -#: widgets.py:418 +#: widgets.py:386 msgid "or" msgstr "ou" -#: widgets.py:423 +#: widgets.py:391 msgid "Start \"hand\" drawing" msgstr "Commencer le tracé manuellement" -#: widgets.py:446 +#: widgets.py:414 msgid "Move on the map" msgstr "Se déplacer" -#: widgets.py:446 +#: widgets.py:414 msgid "Draw" msgstr "Tracer" -#: widgets.py:536 +#: 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:593 +#: widgets.py:561 msgid "Type:" msgstr "Type :" -#: widgets.py:593 +#: widgets.py:561 msgid "Node" msgstr "Nœud" -#: widgets.py:594 +#: widgets.py:562 msgid "Way" msgstr "Route" -#: widgets.py:605 +#: 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' " @@ -1101,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:612 +#: widgets.py:580 msgid "Tag:" msgstr "Clé/valeur :" -#: widgets.py:616 +#: widgets.py:584 msgid "You have to select an area." msgstr "Vous devez sélectionner une zone." -#: widgets.py:618 +#: widgets.py:586 msgid "You have to select a type." msgstr "Vous devez sélectionner un type." -#: widgets.py:620 +#: widgets.py:588 msgid "You have to insert a filter tag." msgstr "Vous devez saisir une clé=valeur." -#: widgets.py:622 +#: 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:625 +#: 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:633 +#: widgets.py:601 msgid "Refresh" msgstr "Rafraîchir" -#: widgets.py:699 +#: widgets.py:667 msgid "Select..." msgstr "Sélectionner..." @@ -1260,55 +1347,68 @@ msgstr "Licence :" msgid "Show multimedia gallery" msgstr "Montrer la galerie multimedia" -#: templates/chimere/detail.html:32 +#: 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:35 templates/chimere/detail.html.py:36 +#: templates/chimere/detail.html:38 templates/chimere/detail.html.py:39 msgid "Propose amendment" msgstr "Proposer une modification" -#: templates/chimere/detail.html:35 +#: 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" -#: templates/chimere/edit.html:114 +#: templates/chimere/edit.html:120 msgid "Personal information" msgstr "Informations personnelles" -#: templates/chimere/edit.html:116 +#: templates/chimere/edit.html:122 msgid "" "This fields are not mandatory. If you provided them they not will be made " "public and they will only used to join you for this project." @@ -1317,38 +1417,62 @@ msgstr "" "pas publiés et ne seront utilisés seulement pour vous joindre dans le cadre " "de ce projet." -#: templates/chimere/edit.html:119 +#: templates/chimere/edit.html:125 msgid "Your name or nickname" msgstr "Votre nom ou pseudo" -#: templates/chimere/edit.html:124 +#: templates/chimere/edit.html:130 msgid "Your email" msgstr "Votre courriel" -#: templates/chimere/edit.html:129 +#: templates/chimere/edit.html:135 msgid "Comments about your submission" msgstr "Commentaires au sujet de votre proposition" -#: templates/chimere/edit.html:135 +#: templates/chimere/edit.html:141 msgid "Upload in progress. Please wait..." msgstr "Dépôt en cours. Veuillez patienter..." -#: templates/chimere/edit.html:153 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:35 +#: 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 !" @@ -1409,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 "Areas:" -msgstr "Zones :" +#: templates/chimere/blocks/areas.html:5 templates/chimere/blocks/maps.html:5 +msgid "Maps:" +msgstr "Cartes :" -#: templates/chimere/blocks/areas_alternative.html:4 -msgid "Shortcuts" -msgstr "Raccourcis" - -#: 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" @@ -1436,10 +1556,6 @@ 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:9 msgid "Loading of the map in progress" msgstr "Chargement de la carte en cours" @@ -1452,7 +1568,7 @@ msgstr "Options d'affichage" msgid "Map type" msgstr "Type de carte" -#: templates/chimere/blocks/map.html:24 +#: templates/chimere/blocks/map.html:25 msgid "Permalink" msgstr "Lien permanent" @@ -1488,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 "" @@ -1499,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" @@ -1539,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" @@ -1592,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" @@ -1607,11 +1751,20 @@ msgstr "Description :" msgid ":" msgstr " :" -#: templatetags/chimere_tags.py:80 +#: 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 :" + #~ msgid "Advanced options" #~ msgstr "Options avancées" @@ -1624,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 Binary files differnew file mode 100644 index 0000000..71ad247 --- /dev/null +++ b/chimere/src/default-bg.xcf diff --git a/chimere/src/default_bg.xcf b/chimere/src/default_bg.xcf Binary files differnew file mode 100644 index 0000000..453bf15 --- /dev/null +++ b/chimere/src/default_bg.xcf diff --git a/chimere/src/marker-cluster-large.xcf b/chimere/src/marker-cluster-large.xcf Binary files differnew file mode 100755 index 0000000..ac0e14d --- /dev/null +++ b/chimere/src/marker-cluster-large.xcf diff --git a/chimere/src/marker-cluster-medium.xcf b/chimere/src/marker-cluster-medium.xcf Binary files differnew file mode 100755 index 0000000..85de110 --- /dev/null +++ b/chimere/src/marker-cluster-medium.xcf diff --git a/chimere/src/marker-cluster-small.xcf b/chimere/src/marker-cluster-small.xcf Binary files differnew file mode 100755 index 0000000..3e38c15 --- /dev/null +++ b/chimere/src/marker-cluster-small.xcf diff --git a/chimere/src/marker-cluster-xlarge.xcf b/chimere/src/marker-cluster-xlarge.xcf Binary files differnew file mode 100755 index 0000000..6c9c890 --- /dev/null +++ b/chimere/src/marker-cluster-xlarge.xcf diff --git a/chimere/src/marker-cluster-xsmall.xcf b/chimere/src/marker-cluster-xsmall.xcf Binary files differnew file mode 100755 index 0000000..f0787cc --- /dev/null +++ b/chimere/src/marker-cluster-xsmall.xcf diff --git a/chimere/src/marker-cluster-xxlarge.xcf b/chimere/src/marker-cluster-xxlarge.xcf Binary files differnew file mode 100755 index 0000000..4c9be7c --- /dev/null +++ b/chimere/src/marker-cluster-xxlarge.xcf diff --git a/chimere/static/chimere/img/share-icon.xcf b/chimere/src/share-icon.xcf Binary files differindex d149768..d149768 100644..100755 --- a/chimere/static/chimere/img/share-icon.xcf +++ b/chimere/src/share-icon.xcf diff --git a/chimere/static/chimere/css/styles.css b/chimere/static/chimere/css/styles.css index 4283782..d79bd0e 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; } @@ -589,6 +614,10 @@ ul.share li{ font-style:italic; } +.nominatim-search{ + font-style:normal; +} + .simple #panel{ top:5px; bottom:auto; @@ -713,6 +742,11 @@ p.warning{ padding:0.5em; } +p.submit{ + border-top-width:1px; + text-align:center; +} + #no-js-message{ z-index:5000; text-align:center; @@ -1182,3 +1216,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 Binary files differnew file mode 100644 index 0000000..085ccae --- /dev/null +++ b/chimere/static/chimere/img/ajax-loader-small.gif diff --git a/chimere/static/chimere/img/default-bg.jpg b/chimere/static/chimere/img/default-bg.jpg Binary files differnew file mode 100644 index 0000000..a3e96f0 --- /dev/null +++ b/chimere/static/chimere/img/default-bg.jpg diff --git a/chimere/static/chimere/img/marker-cluster-large.png b/chimere/static/chimere/img/marker-cluster-large.png Binary files differnew file mode 100644 index 0000000..eef6a73 --- /dev/null +++ b/chimere/static/chimere/img/marker-cluster-large.png diff --git a/chimere/static/chimere/img/marker-cluster-medium.png b/chimere/static/chimere/img/marker-cluster-medium.png Binary files differnew file mode 100644 index 0000000..1d9099b --- /dev/null +++ b/chimere/static/chimere/img/marker-cluster-medium.png diff --git a/chimere/static/chimere/img/marker-cluster-small.png b/chimere/static/chimere/img/marker-cluster-small.png Binary files differnew file mode 100644 index 0000000..0620694 --- /dev/null +++ b/chimere/static/chimere/img/marker-cluster-small.png diff --git a/chimere/static/chimere/img/marker-cluster-xlarge.png b/chimere/static/chimere/img/marker-cluster-xlarge.png Binary files differnew file mode 100644 index 0000000..477e2b8 --- /dev/null +++ b/chimere/static/chimere/img/marker-cluster-xlarge.png diff --git a/chimere/static/chimere/img/marker-cluster-xsmall.png b/chimere/static/chimere/img/marker-cluster-xsmall.png Binary files differnew file mode 100644 index 0000000..748b3ed --- /dev/null +++ b/chimere/static/chimere/img/marker-cluster-xsmall.png diff --git a/chimere/static/chimere/img/marker-cluster-xxlarge.png b/chimere/static/chimere/img/marker-cluster-xxlarge.png Binary files differnew file mode 100644 index 0000000..d7b435f --- /dev/null +++ b/chimere/static/chimere/img/marker-cluster-xxlarge.png 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 e40b071..ae3e340 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_route_feature: null, // To store the current route 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( @@ -682,7 +702,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"; @@ -1069,6 +1089,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 Binary files differnew file mode 100644 index 0000000..ef90a08 --- /dev/null +++ b/chimere/static/leaflet/images/layers.png diff --git a/chimere/static/leaflet/images/marker-icon.png b/chimere/static/leaflet/images/marker-icon.png Binary files differnew file mode 100644 index 0000000..e2e9f75 --- /dev/null +++ b/chimere/static/leaflet/images/marker-icon.png diff --git a/chimere/static/leaflet/images/marker-icon@2x.png b/chimere/static/leaflet/images/marker-icon@2x.png Binary files differnew file mode 100644 index 0000000..0015b64 --- /dev/null +++ b/chimere/static/leaflet/images/marker-icon@2x.png diff --git a/chimere/static/leaflet/images/marker-shadow.png b/chimere/static/leaflet/images/marker-shadow.png Binary files differnew file mode 100644 index 0000000..d1e773c --- /dev/null +++ b/chimere/static/leaflet/images/marker-shadow.png 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 = '×'; + + 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(' — '); + }, + + _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="×",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(" — ")}},_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'> </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 a7b9347..b2210e2 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 %} @@ -29,9 +29,12 @@ <a href='#' class='show_gallery_link'>{% trans "Show multimedia gallery" %}</a> </p>{% endif %} </div> - <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 15b8afa..3c48d95 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,15 +72,15 @@ 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 - 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 @@ -91,16 +91,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) @@ -125,21 +125,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, @@ -147,7 +149,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, @@ -192,59 +196,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: @@ -258,9 +271,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 \ @@ -302,10 +315,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'> </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): |