# coding: utf-8 # maposmatic, the web front-end of the MapOSMatic city map generation system # Copyright (C) 2009 David Decotigny # Copyright (C) 2009 Frédéric Lehobey # Copyright (C) 2009 Pierre Mauduit # Copyright (C) 2009 David Mentré # Copyright (C) 2009 Maxime Petazzoni # Copyright (C) 2009 Thomas Petazzoni # Copyright (C) 2009 Gaël Utard # Copyright (C) 2019 Hartmut Holzgraefe # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as # published by the Free Software Foundation, either version 3 of the # License, or 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 Affero General Public License for more details. # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . # Forms for MapOSMatic from django import forms from django.utils.safestring import mark_safe from django.utils.html import escape from django.utils.translation import ugettext_lazy as _ from django.forms.utils import ErrorList import time import ocitysmap from www.maposmatic import models, widgets import www.settings class MapSearchForm(forms.Form): """ The map search form, allowing search through the rendered maps. """ query = forms.CharField(min_length=1, required=True, widget=forms.TextInput(attrs= {'placeholder': _('Search for a map'), 'class': 'span2'})) class MapRenderingJobForm(forms.ModelForm): """ The main map rendering form, displayed on the 'Create Map' page. It's a ModelForm based on the MapRenderingJob model. """ class Meta: model = models.MapRenderingJob fields = ('layout', 'stylesheet', 'overlay', 'maptitle', 'administrative_city', 'lat_upper_left', 'lon_upper_left', 'lat_bottom_right', 'lon_bottom_right', 'track', 'umap', 'submittermail') ORIENTATION = (('portrait', mark_safe(" %s" % _('Portrait'))), ('landscape', mark_safe(" %s" % _('Landscape')))) mode = forms.CharField(initial='bbox', widget=forms.HiddenInput) layout = forms.ChoiceField(choices=(), widget=forms.RadioSelect(attrs= { 'onchange' : '$("#layout-preview").attr("src","/media/img/layout/"+this.value+".png");'})) stylesheet = forms.ChoiceField(choices=(), widget=forms.Select(attrs= { 'onchange' : '$("#style-preview").attr("src","/media/img/style/"+this.value+".jpg");'})) overlay = forms.MultipleChoiceField(choices=(), widget=forms.SelectMultiple(attrs= { 'class': 'multipleSelect' }), required=False) papersize = forms.ChoiceField(choices=(), widget=forms.RadioSelect) paperorientation = forms.ChoiceField(choices=ORIENTATION, widget=forms.RadioSelect) paper_width_mm = forms.IntegerField(widget=forms.HiddenInput) paper_height_mm = forms.IntegerField(widget=forms.HiddenInput) maptitle = forms.CharField(max_length=256, required=False) bbox = widgets.AreaField(label=_("Area"), fields=(forms.FloatField(), forms.FloatField(), forms.FloatField(), forms.FloatField())) map_lang_flag_list = [] for lang_key, lang_name in www.settings.MAP_LANGUAGES_LIST: if lang_key == 'C': map_lang_flag_list.append((lang_key, lang_name)) else: country_code = lang_key[3:5].lower() lang_html = mark_safe(" %s" % (country_code, lang_name)) map_lang_flag_list.append((lang_key, lang_html)) map_language = forms.ChoiceField(choices=map_lang_flag_list, widget=forms.Select( attrs={'style': 'min-width: 200px'})) administrative_osmid = forms.IntegerField(widget=forms.HiddenInput, required=False) def __init__(self, *args, **kwargs): super(MapRenderingJobForm, self).__init__(*args, **kwargs) self._ocitysmap = ocitysmap.OCitySMap(www.settings.OCITYSMAP_CFG_PATH) layout_renderers = self._ocitysmap.get_all_renderers() stylesheets = self._ocitysmap.get_all_style_configurations() overlays = self._ocitysmap.get_all_overlay_configurations() self.fields['layout'].choices = [] # TODO move descriptions to ocitysmap side for r in layout_renderers: if r.name == 'plain': description = _(u"Full-page layout without street index") elif r.name == 'single_page_index_side': description = _(u"Full-page layout with the street index on the side") elif r.name == 'single_page_index_bottom': description = _(u"Full-page layout with the street index at the bottom") elif r.name == 'multi_page': description = _(u"Multi-page layout") else: description = mark_safe(_(u"The %(layout_name)s layout") % {'layout_name':r.name}) self.fields['layout'].choices.append((r.name, description)) if not self.fields['layout'].initial: self.fields['layout'].initial = layout_renderers[0].name style_choices = {} # TODO fetch descriptions from style config file for s in stylesheets: if s.description is not None: description = mark_safe(escape(s.description)) elif s.name == "Default": description = _("The default OpenStreetMap.org style") elif s.name == "MapQuestEu": description = _("The european MapQuest style") elif s.name == "MapQuestUs": description = _("The US MapQuest style") elif s.name == "MapQuestUk": description = _("The UK MapQuest style") elif s.name == "Printable": description = _("A MapOSMatic-specific stylesheet suitable for printing") else: description = mark_safe(_("The %(stylesheet_name)s stylesheet") % {'stylesheet_name':s.name}) if s.url: description = mark_safe("%s " % (description, s.url, _("more info"))) if s.group not in style_choices: style_choices[s.group] = [] style_choices[s.group].append((s.name, description)) grouped_choices = [] for name, members in style_choices.items(): grouped_choices.append([name, members]) self.fields['stylesheet'].choices = grouped_choices; if not self.fields['stylesheet'].initial: self.fields['stylesheet'].initial = stylesheets[0].name overlay_choices = {} for s in overlays: if s.description is not None: description = mark_safe(escape(s.description)) else: description = mark_safe(_("The %(stylesheet_name)s overlay") % {'stylesheet_name':s.name}) if s.url: description = mark_safe("%s " % (description, s.url, _("more info"))) if s.group not in overlay_choices: overlay_choices[s.group] = [] overlay_choices[s.group].append((s.name, description)) grouped_choices = [] for name, members in overlay_choices.items(): grouped_choices.append([name, members]) self.fields['overlay'].choices = grouped_choices; if not self.fields['overlay'].initial: self.fields['overlay'].initial = '' def _build_papersize_description(p): if p[0] == "Best fit": return mark_safe(_("Best fit ")) else: return mark_safe("%s " "(%.1f × %.1f cm²)" % (p[0], p[1] / 10., p[2] / 10.)) self.fields['papersize'].choices = [ (p[0], _build_papersize_description(p)) for p in self._ocitysmap.get_all_paper_sizes()] def clean(self): """Cleanup function for the map query form. Different checks are required depending on the selected mode (by admininstrative city, or by bounding box). Returns the cleaned_data array. """ cleaned_data = self.cleaned_data mode = cleaned_data.get("mode") city = cleaned_data.get("administrative_city") title = cleaned_data.get("maptitle") layout = cleaned_data.get("layout") stylesheet = cleaned_data.get("stylesheet") overlay_array = [] for overlay in cleaned_data.get("overlay"): overlay_array.append(overlay) overlay = ",".join(overlay_array) if cleaned_data.get("paperorientation") == 'landscape': cleaned_data["paper_width_mm"], cleaned_data["paper_height_mm"] = \ cleaned_data.get("paper_height_mm"), cleaned_data.get("paper_width_mm") if layout == '': msg = _(u"Layout required") self._errors["layout"] = ErrorList([msg]) del cleaned_data["layout"] if stylesheet == '': msg = _(u"Stylesheet required") self._errors["stylesheet"] = ErrorList([msg]) del cleaned_data["stylesheet"] if mode == 'admin': if city == "": msg = _(u"Administrative city required") self._errors["administrative_city"] = ErrorList([msg]) del cleaned_data["administrative_city"] # Make sure that bbox and admin modes are exclusive cleaned_data["lat_upper_left"] = None cleaned_data["lon_upper_left"] = None cleaned_data["lat_bottom_right"] = None cleaned_data["lon_bottom_right"] = None try: self._check_osm_id(cleaned_data.get("administrative_osmid")) except Exception as ex: msg = _(u"Error with osm city: %s" % ex) self._errors['administrative_osmid'] \ = ErrorList([msg]) elif mode == 'bbox': # Check bounding box corners are provided for f in [ "lat_upper_left", "lon_upper_left", "lat_bottom_right", "lon_bottom_right" ]: val = cleaned_data.get(f) if val is None: msg = _(u"Required") self._errors['bbox'] = ErrorList([msg]) if f in cleaned_data: del cleaned_data[f] # Check latitude and longitude are different if (cleaned_data.get("lat_upper_left") == cleaned_data.get("lat_bottom_right")): msg = _(u"Same latitude") self._errors['bbox'] = ErrorList([msg]) del cleaned_data["lat_upper_left"] del cleaned_data["lat_bottom_right"] if (cleaned_data.get("lon_upper_left") == cleaned_data.get("lon_bottom_right")): msg = _(u"Same longitude") self._errors['bbox'] = ErrorList([msg]) del cleaned_data["lon_upper_left"] del cleaned_data["lon_bottom_right"] # Make sure that bbox and admin modes are exclusive cleaned_data["administrative_city"] = '' cleaned_data["administrative_osmid"] = None # Don't try to instanciate a bounding box with empty coordinates if self._errors: return cleaned_data lat_upper_left = cleaned_data.get("lat_upper_left") lon_upper_left = cleaned_data.get("lon_upper_left") lat_bottom_right = cleaned_data.get("lat_bottom_right") lon_bottom_right = cleaned_data.get("lon_bottom_right") boundingbox = ocitysmap.coords.BoundingBox( lat_upper_left, lon_upper_left, lat_bottom_right, lon_bottom_right) (metric_size_lat, metric_size_long) = boundingbox.spheric_sizes() if (metric_size_lat > www.settings.BBOX_MAXIMUM_LENGTH_IN_METERS or metric_size_long > www.settings.BBOX_MAXIMUM_LENGTH_IN_METERS): msg = _(u"Bounding Box too large") self._errors['bbox'] = ErrorList([msg]) return cleaned_data def _check_osm_id(self, osm_id): """Make sure that the supplied OSM Id is valid and can be accepted for rendering (bounding box not too large, etc.). Raise an exception in case of error.""" bbox_wkt, area_wkt = self._ocitysmap.get_geographic_info(osm_id) bbox = ocitysmap.coords.BoundingBox.parse_wkt(bbox_wkt) (metric_size_lat, metric_size_long) = bbox.spheric_sizes() if metric_size_lat > www.settings.BBOX_MAXIMUM_LENGTH_IN_METERS or \ metric_size_long > www.settings.BBOX_MAXIMUM_LENGTH_IN_METERS: raise ValueError("Area too large") class MapPaperSizeForm(forms.Form): """ The map paper size form, which is only used to analyze the arguments of the POST request to /apis/papersize/ """ osmid = forms.IntegerField(required=False) layout = forms.CharField(max_length=256) stylesheet = forms.CharField(max_length=256) lat_upper_left = forms.FloatField(required=False, min_value=-90.0, max_value=90.0) lon_upper_left = forms.FloatField(required=False, min_value=-180.0, max_value=180.0) lat_bottom_right = forms.FloatField(required=False, min_value=-90.0, max_value=90.0) lon_bottom_right = forms.FloatField(required=False, min_value=-180.0, max_value=180.0) class MapRecreateForm(forms.Form): """ The map recreate form, to reschedule an already processed job on the queue. """ id = forms.IntegerField(widget=forms.HiddenInput, required=True) def clean(self): cleaned_data = self.cleaned_data try: cleaned_data["id"] = int(cleaned_data.get("id", 0)) except ValueError: cleaned_data["id"] = 0 return cleaned_data class MapCancelForm(forms.Form): """ The map cancel form, to cancel a job (when the user has the matching nonce). """ id = forms.IntegerField(widget=forms.HiddenInput, required=True) nonce = forms.CharField(widget=forms.HiddenInput, required=True) def clean(self): cleaned_data = self.cleaned_data try: cleaned_data["id"] = int(cleaned_data.get("id", 0)) except ValueError: cleaned_data["id"] = 0 return cleaned_data