Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added optional altitude field #54

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 12 additions & 4 deletions geoposition/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
10 changes: 8 additions & 2 deletions geoposition/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -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(',')
Expand All @@ -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)
Expand Down
17 changes: 12 additions & 5 deletions geoposition/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 ""
Binary file added geoposition/static/geoposition/ajax-loader.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
44 changes: 41 additions & 3 deletions geoposition/static/geoposition/geoposition.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -36,6 +41,9 @@ if (jQuery != undefined) {
$searchInput = $('<input>', {'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,
Expand All @@ -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') || {};
Expand Down Expand Up @@ -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()) {
Expand Down
17 changes: 17 additions & 0 deletions geoposition/templates/geoposition/widgets/geoposition.html
Original file line number Diff line number Diff line change
@@ -1,12 +1,29 @@
{% load static from staticfiles %}
<div class="geoposition-widget" data-map-widget-height="{{ config.map_widget_height }}" data-map-options="{{ config.map_options }}" data-marker-options="{{ config.marker_options }}">
<table>
<tr>
<td>{{ latitude.label|capfirst }}</td>
<td>{{ latitude.html }}</td>
{% if altitude.available %}
<td><span class="altitude-loading"><img src="{% static 'geoposition/ajax-loader.gif' %}"/></span></td>
{% endif %}
</tr>
<tr>
<td>{{ longitude.label|capfirst }}</td>
<td>{{ longitude.html }}</td>
{% if altitude.available %}
<td><span class="altitude-loading"><img src="{% static 'geoposition/ajax-loader.gif' %}"/></span></td>
{% endif %}
</tr>
{% if altitude.available %}
<tr>
<td>{{ altitude.label|capfirst }}</td>
<td>{{ altitude.html }}</td>
<td>
<a href="javascript:void(0);" class="altitude-error" style="color:red; font-weight: bold">( ! )</a>
<span class="altitude-loading"><img src="{% static 'geoposition/ajax-loader.gif' %}"/></span>
</td>
</tr>
{% endif %}
</table>
</div>
29 changes: 24 additions & 5 deletions geoposition/tests/test_geoposition.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,49 +6,68 @@

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)

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)
17 changes: 13 additions & 4 deletions geoposition/widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -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', {
Expand All @@ -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),
Expand Down