diff --git a/geoposition/__init__.py b/geoposition/__init__.py index 8fee622..17f1cea 100644 --- a/geoposition/__init__.py +++ b/geoposition/__init__.py @@ -7,17 +7,23 @@ class Geoposition(object): - def __init__(self, latitude, longitude): + def __init__(self, latitude, longitude, altitude=None): if isinstance(latitude, float) or isinstance(latitude, int): latitude = str(latitude) if isinstance(longitude, float) or isinstance(longitude, int): longitude = str(longitude) + if isinstance(altitude, float) or isinstance(altitude, int): + altitude = str(altitude) self.latitude = Decimal(latitude) self.longitude = Decimal(longitude) + self.altitude = Decimal(altitude) if not altitude is None else None def __str__(self): - return "%s,%s" % (self.latitude, self.longitude) + r = "%s,%s" % (self.latitude, self.longitude) + if not self.altitude is None: + return "%s,%s" % (r, self.altitude) + return r def __repr__(self): return "Geoposition(%s)" % str(self) @@ -26,7 +32,9 @@ def __len__(self): return len(str(self)) def __eq__(self, other): - return isinstance(other, Geoposition) and self.latitude == other.latitude and self.longitude == other.longitude + return (isinstance(other, Geoposition) and self.latitude == other.latitude + and self.longitude == other.longitude and self.altitude == other.altitude) def __ne__(self, other): - return not isinstance(other, Geoposition) or self.latitude != other.latitude or self.longitude != other.longitude + return (not isinstance(other, Geoposition) or self.latitude != other.latitude + or self.longitude != other.longitude or self.altitude != other.altitude) diff --git a/geoposition/fields.py b/geoposition/fields.py index 6e8209c..ebd7c9d 100644 --- a/geoposition/fields.py +++ b/geoposition/fields.py @@ -14,7 +14,9 @@ class GeopositionField(with_metaclass(models.SubfieldBase, models.Field)): def __init__(self, *args, **kwargs): kwargs['max_length'] = 42 + altitude_available = kwargs.pop('elevation', False) super(GeopositionField, self).__init__(*args, **kwargs) + self.altitude_available = altitude_available def get_internal_type(self): return 'CharField' @@ -25,7 +27,7 @@ def to_python(self, value): if isinstance(value, Geoposition): return value if isinstance(value, list): - return Geoposition(value[0], value[1]) + return Geoposition(*value) # default case is string value_parts = value.rsplit(',') @@ -37,8 +39,12 @@ def to_python(self, value): longitude = value_parts[1] except IndexError: longitude = '0.0' + try: + altitude = value_parts[2] + except IndexError: + altitude = None - return Geoposition(latitude, longitude) + return Geoposition(latitude, longitude, altitude) def get_prep_value(self, value): return str(value) diff --git a/geoposition/forms.py b/geoposition/forms.py index 02c4954..c7da4e7 100644 --- a/geoposition/forms.py +++ b/geoposition/forms.py @@ -6,28 +6,35 @@ from .widgets import GeopositionWidget from . import Geoposition - class GeopositionField(forms.MultiValueField): default_error_messages = { 'invalid': _('Enter a valid geoposition.') } def __init__(self, *args, **kwargs): + altitude_available = kwargs.pop('elevation', False) self.widget = GeopositionWidget() - fields = ( + + fields = [ forms.DecimalField(label=_('latitude')), forms.DecimalField(label=_('longitude')), - ) + ] + + if altitude_available: + fields.append(forms.DecimalField(label=_('altitude'), required=False)) + if 'initial' in kwargs: kwargs['initial'] = Geoposition(*kwargs['initial'].split(',')) + + self.altitude_available = altitude_available super(GeopositionField, self).__init__(fields, **kwargs) def widget_attrs(self, widget): classes = widget.attrs.get('class', '').split() classes.append('geoposition') - return {'class': ' '.join(classes)} + return {'class': ' '.join(classes), 'altitude': self.altitude_available} def compress(self, value_list): if value_list: return value_list - return "" + return "" \ No newline at end of file diff --git a/geoposition/static/geoposition/ajax-loader.gif b/geoposition/static/geoposition/ajax-loader.gif new file mode 100644 index 0000000..f2a1bc0 Binary files /dev/null and b/geoposition/static/geoposition/ajax-loader.gif differ diff --git a/geoposition/static/geoposition/geoposition.js b/geoposition/static/geoposition/geoposition.js index eab8895..19bcdf4 100644 --- a/geoposition/static/geoposition/geoposition.js +++ b/geoposition/static/geoposition/geoposition.js @@ -18,6 +18,11 @@ if (jQuery != undefined) { var mapDefaults = { 'mapTypeId': google.maps.MapTypeId.ROADMAP, + 'mapTypeControlOptions': { + 'mapTypeIds': [google.maps.MapTypeId.ROADMAP, + google.maps.MapTypeId.TERRAIN, + google.maps.MapTypeId.HYBRID] + }, 'scrollwheel': false, 'streetViewControl': false, 'panControl': false @@ -36,6 +41,9 @@ if (jQuery != undefined) { $searchInput = $('', {'type': 'search', 'placeholder': 'Start typing an address …'}), $latitudeField = $container.find('input.geoposition:eq(0)'), $longitudeField = $container.find('input.geoposition:eq(1)'), + $elevationField = $container.find('input.geoposition:eq(2)'), + $elevationLoading = $container.find('.altitude-loading').hide(), + $elevationError = $container.find('.altitude-error:eq(0)'), latitude = parseFloat($latitudeField.val()) || null, longitude = parseFloat($longitudeField.val()) || null, map, @@ -44,7 +52,8 @@ if (jQuery != undefined) { mapCustomOptions, markerOptions, markerCustomOptions, - marker; + marker, + elevator = $elevationField.length > 0 ? new google.maps.ElevationService() : null; $mapContainer.css('height', $container.data('map-widget-height') + 'px'); mapCustomOptions = $container.data('map-options') || {}; @@ -144,9 +153,38 @@ if (jQuery != undefined) { } marker = new google.maps.Marker(markerOptions); + google.maps.event.addListener(marker, 'dragend', function() { - $latitudeField.val(this.position.lat()); - $longitudeField.val(this.position.lng()); + var lat = this.position.lat(), + lng = this.position.lng(), + latLng = new google.maps.LatLng(lat, lng); + + if (elevator) { + $elevationLoading.show(); + $elevationError.hide(); + + elevator.getElevationForLocations({ + 'locations': [latLng] + }, function (results, status) { + $latitudeField.val(lat); + $longitudeField.val(lng); + + if (status === google.maps.ElevationStatus.OK) { + // Retrieve the first result + if (results[0]) { + $elevationField.val(results[0].elevation); + } else { + $elevationError.attr('title', "Elevation: No results found").show(); + } + } else { + $elevationError.attr('title', 'Elevation service failed due to: ' + status).show(); + } + $elevationLoading.hide(); + }); + } else { + $latitudeField.val(lat); + $longitudeField.val(lng); + } doGeocode(); }); if ($latitudeField.val() && $longitudeField.val()) { diff --git a/geoposition/templates/geoposition/widgets/geoposition.html b/geoposition/templates/geoposition/widgets/geoposition.html index e621ad4..acb98cf 100644 --- a/geoposition/templates/geoposition/widgets/geoposition.html +++ b/geoposition/templates/geoposition/widgets/geoposition.html @@ -1,12 +1,29 @@ +{% load static from staticfiles %}
+ {% if altitude.available %} + + {% endif %} + {% if altitude.available %} + + {% endif %} + {% if altitude.available %} + + + + + + {% endif %}
{{ latitude.label|capfirst }} {{ latitude.html }}
{{ longitude.label|capfirst }} {{ longitude.html }}
{{ altitude.label|capfirst }}{{ altitude.html }} + ( ! ) + +
diff --git a/geoposition/tests/test_geoposition.py b/geoposition/tests/test_geoposition.py index dab469c..d0a890c 100644 --- a/geoposition/tests/test_geoposition.py +++ b/geoposition/tests/test_geoposition.py @@ -6,45 +6,60 @@ class GeopositionTestCase(SimpleTestCase): def test_init_with_decimals(self): - gp = Geoposition(Decimal('52.5'), Decimal('13.4')) + gp = Geoposition(Decimal('52.5'), Decimal('13.4'), Decimal('2.5')) self.assertEqual(gp.latitude, Decimal('52.5')) self.assertEqual(gp.longitude, Decimal('13.4')) + self.assertEqual(gp.altitude, Decimal('2.5')) def test_init_with_strs(self): - gp = Geoposition('52.5', '13.4') + gp = Geoposition('52.5', '13.4', '2.5') self.assertEqual(gp.latitude, Decimal('52.5')) self.assertEqual(gp.longitude, Decimal('13.4')) + self.assertEqual(gp.altitude, Decimal('2.5')) def test_init_with_floats(self): - gp = Geoposition(52.5, 13.4) + gp = Geoposition(52.5, 13.4, 2.5) self.assertEqual(gp.latitude, Decimal('52.5')) self.assertEqual(gp.longitude, Decimal('13.4')) + self.assertEqual(gp.altitude, Decimal('2.5')) def test_repr(self): gp = Geoposition(52.5, 13.4) self.assertEqual(repr(gp), 'Geoposition(52.5,13.4)') + gp = Geoposition(52.5, 13.4, 2.5) + self.assertEqual(repr(gp), 'Geoposition(52.5,13.4,2.5)') def test_len(self): gp = Geoposition(52.5, 13.4) self.assertEqual(len(gp), 9) + gp = Geoposition(52.5, 13.4, 2.5) + self.assertEqual(len(gp), 13) def test_equality(self): gp1 = Geoposition(52.5, 13.4) gp2 = Geoposition(52.5, 13.4) self.assertEqual(gp1, gp2) + gp1 = Geoposition(52.5, 13.4, 2.5) + gp2 = Geoposition(52.5, 13.4, 2.5) + self.assertEqual(gp1, gp2) + def test_inequality(self): gp1 = Geoposition(52.5, 13.4) gp2 = Geoposition(52.4, 13.1) self.assertNotEqual(gp1, gp2) + gp1 = Geoposition(52.5, 13.4, 2.5) + gp2 = Geoposition(52.5, 13.4, 2.3) + self.assertNotEqual(gp1, gp2) + def test_equality_with_none(self): - gp1 = Geoposition(52.5, 13.4) + gp1 = Geoposition(52.5, 13.4, 2.5) gp2 = None self.assertFalse(gp1 == gp2) def test_inequality_with_none(self): - gp1 = Geoposition(52.5, 13.4) + gp1 = Geoposition(52.5, 13.4, 2.5) gp2 = None self.assertTrue(gp1 != gp2) @@ -52,3 +67,7 @@ def test_db_value_to_python_object(self): obj = PointOfInterest.objects.create(name='Foo', address='some where', city='city', zipcode='12345', position=Geoposition(52.5,13.4)) poi = PointOfInterest.objects.get(id=obj.id) self.assertIsInstance(poi.position, Geoposition) + + obj = PointOfInterest.objects.create(name='Foo', address='some where', city='city', zipcode='12345', position=Geoposition(52.5,13.4, 2.5)) + poi = PointOfInterest.objects.get(id=obj.id) + self.assertIsInstance(poi.position, Geoposition) diff --git a/geoposition/widgets.py b/geoposition/widgets.py index 38a3957..c7922b0 100644 --- a/geoposition/widgets.py +++ b/geoposition/widgets.py @@ -10,18 +10,22 @@ class GeopositionWidget(forms.MultiWidget): def __init__(self, attrs=None): - widgets = ( + widgets = [ forms.TextInput(), forms.TextInput(), - ) + forms.TextInput(), + ] super(GeopositionWidget, self).__init__(widgets, attrs) def decompress(self, value): if isinstance(value, six.text_type): return value.rsplit(',') if value: - return [value.latitude, value.longitude] - return [None,None] + if hasattr(value, 'altitude'): + return [value.latitude, value.longitude, value.altitude] + else: + return [value.latitude, value.longitude] + return [None, None, None] def format_output(self, rendered_widgets): return render_to_string('geoposition/widgets/geoposition.html', { @@ -33,6 +37,11 @@ def format_output(self, rendered_widgets): 'html': rendered_widgets[1], 'label': _("longitude"), }, + 'altitude': { + 'available': self.attrs['altitude'], + 'html': rendered_widgets[2], + 'label': _("altitude"), + }, 'config': { 'map_widget_height': settings.GEOPOSITION_MAP_WIDGET_HEIGHT, 'map_options': json.dumps(settings.GEOPOSITION_MAP_OPTIONS),