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

Add Leaflet backend to enable custom map providers e.g. OpenStreetMap #94

Open
wants to merge 9 commits into
base: django-1.11
Choose a base branch
from
Open
6 changes: 6 additions & 0 deletions .csslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"ignore": [
"order-alphabetical",
"important"
]
}
8 changes: 8 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
root = true

[*]
charset = utf-8
end_of_line = lf # Unix-style newlines
insert_final_newline = true
indent_style = space
indent_size = 4
35 changes: 35 additions & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"extends": "eslint:recommended",

"env": {
"browser": true,
"jquery": true,
},

"globals": {
"google": true,
"L": true,
},

"rules": {
"quotes": ["warn", "single"],
"semi": ["warn", "always"],
"comma-dangle": ["warn", "never"],

"object-curly-spacing": "warn",
"block-spacing": "warn",

"brace-style": ["warn", "1tbs", { "allowSingleLine": true }],
"key-spacing": "warn",
"keyword-spacing": "warn",
"space-before-blocks": "warn",
"func-call-spacing": "warn",

"no-tabs": "warn",
"no-trailing-spaces": "warn",
"eol-last": "warn",

"eqeqeq": "off",
"no-console": "off",
},
}
3 changes: 3 additions & 0 deletions .flake8
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[flake8]
# E501 - line too long (999 > 79 characters)
ignore = E501
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ MANIFEST
*.egg
*.sqlite3
docs/_build/
/.env
1 change: 1 addition & 0 deletions example/apps.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from django.apps import AppConfig


class ExampleConfig(AppConfig):
name = 'example'
verbose_name = "Example"
3 changes: 0 additions & 3 deletions example/tests.py

This file was deleted.

1 change: 1 addition & 0 deletions example/views.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from django.shortcuts import render
from .models import PointOfInterest


def poi_list(request):
pois = PointOfInterest.objects.all()
return render(request, 'poi_list.html', {'pois': pois})
1 change: 1 addition & 0 deletions geoposition/apps.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from django.apps import AppConfig


class GeoPositionConfig(AppConfig):
name = 'geoposition'
verbose_name = "GeoPosition"
10 changes: 6 additions & 4 deletions geoposition/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,19 @@ class AppSettings(object):
'MAP_OPTIONS': {},
'MARKER_OPTIONS': {},
'GOOGLE_MAPS_API_KEY': None,
'BACKEND': 'google',
}
prefix = 'GEOPOSITION'
required_settings = ['GOOGLE_MAPS_API_KEY']
required_settings = [('google', 'GOOGLE_MAPS_API_KEY')]

def __init__(self, django_settings):
self.django_settings = django_settings

for setting in self.required_settings:
for backend, setting in self.required_settings:
prefixed_name = '%s_%s' % (self.prefix, setting)
if not hasattr(self.django_settings, prefixed_name):
raise ImproperlyConfigured("The '%s' setting is required." % prefixed_name)
if backend == getattr(self.django_settings, self.prefix + '_BACKEND', self.defaults['BACKEND']):
if not hasattr(self.django_settings, prefixed_name):
raise ImproperlyConfigured("The '%s' setting is required." % prefixed_name)

def __getattr__(self, name):
prefixed_name = '%s_%s' % (self.prefix, name)
Expand Down
1 change: 0 additions & 1 deletion geoposition/fields.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from __future__ import unicode_literals

from django.db import models
from django.utils.six import with_metaclass
from django.utils.translation import ugettext_lazy as _
from django.utils.encoding import smart_text

Expand Down
Binary file modified geoposition/locale/cs/LC_MESSAGES/django.mo
Binary file not shown.
Binary file added geoposition/locale/de/LC_MESSAGES/django.mo
Binary file not shown.
34 changes: 34 additions & 0 deletions geoposition/locale/de/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-09-10 19:29+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <[email protected]>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: geoposition/fields.py:12
msgid "A geoposition (latitude and longitude)"
msgstr "Eine Koordinate (Dezimalgrad)"

#: geoposition/forms.py:12
msgid "Enter a valid geoposition."
msgstr "Bitte gültige Dezimalgradkoordinaten eingeben."

#: geoposition/forms.py:18 geoposition/widgets.py:65 geoposition/widgets.py:79
msgid "latitude"
msgstr "Breitengrad"

#: geoposition/forms.py:19 geoposition/widgets.py:69 geoposition/widgets.py:83
msgid "longitude"
msgstr "Längengrad"
Binary file modified geoposition/locale/ru/LC_MESSAGES/django.mo
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
if (jQuery != undefined) {
var django = {
'jQuery': jQuery,
}
'jQuery': jQuery
};
}


Expand All @@ -10,9 +10,9 @@ if (jQuery != undefined) {
$(document).ready(function() {

try {
var _ = google;
var _ = google; // eslint-disable-line no-unused-vars
} catch (ReferenceError) {
console.log('geoposition: "google" not defined. You might not be connected to the internet.');
console.log('geoposition: "google" not defined. You might not be connected to the internet.');
return;
}

Expand Down Expand Up @@ -90,7 +90,7 @@ if (jQuery != undefined) {
var gc = new google.maps.Geocoder();
gc.geocode({
'latLng': marker.position
}, function(results, status) {
}, function(results) {
$addressRow.text('');
if (results && results[0]) {
$addressRow.text(results[0].formatted_address);
Expand All @@ -109,10 +109,9 @@ if (jQuery != undefined) {
if (e.keyCode == 13) {
e.preventDefault();
doSearch();
}
else {
} else {
// otherwise, search after a while after typing ends
autoSuggestTimer = setTimeout(function(){
autoSuggestTimer = setTimeout(function() {
doSearch();
}, 1000);
}
Expand Down Expand Up @@ -153,7 +152,7 @@ if (jQuery != undefined) {
google.maps.event.trigger(marker, 'dragend');
}

$latitudeField.add($longitudeField).bind('keyup', function(e) {
$latitudeField.add($longitudeField).bind('keyup', function() {
var latitude = parseFloat($latitudeField.val()) || 0;
var longitude = parseFloat($longitudeField.val()) || 0;
var center = new google.maps.LatLng(latitude, longitude);
Expand Down
109 changes: 109 additions & 0 deletions geoposition/static/geoposition/leaflet.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
if (jQuery != undefined) {
var django = {
'jQuery': jQuery
};
}


(function($) {

$(document).ready(function() {

try {
var _ = L; // eslint-disable-line no-unused-vars
} catch (ReferenceError) {
console.log('geoposition: "L" not defined. You might not be connected to the internet.');
return;
}

$('.geoposition-widget').each(function() {
var $container = $(this),
$mapContainer = $('<div class="geoposition-map" />'),
$latitudeField = $container.find('input.geoposition:eq(0)'),
$longitudeField = $container.find('input.geoposition:eq(1)'),
latitude = parseFloat($latitudeField.val()) || null,
longitude = parseFloat($longitudeField.val()) || null,
mapOptions = {
url: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
attribution: '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>',
maxZoom: 19,
dataZoom: 16,
initialZoom: 2,
initialCenter: [25, 0]
},
mapNonProviderOptions = ['url', 'dataZoom', 'initialZoom', 'initialCenter'],
mapProviderOptions = {},
mapCustomOptions,
map,
marker;

$mapContainer.css('height', $container.attr('data-map-widget-height') + 'px');
mapCustomOptions = JSON.parse($container.attr('data-map-options'));
$.extend(mapOptions, mapCustomOptions);

for (var option in mapOptions) {
if (mapNonProviderOptions.includes(option) === false)
mapProviderOptions[option] = mapOptions[option];
}

function setLatLng(latLng) {
$latitudeField.val(latLng.lat);
$longitudeField.val(latLng.lng);
}

function getLatLng() {
latitude = parseFloat($latitudeField.val()) || null;
longitude = parseFloat($longitudeField.val()) || null;
return {lat: latitude, lng: longitude};
}

function mapClickListen(e) {
setMarker(e.latlng);
}

function setMarker(latLng) {
if (marker) marker.remove();
marker = L.marker(latLng, {draggable: true});
marker.on('dragend', function(e) {
setLatLng(e.target.getLatLng());
map.panTo(e.target.getLatLng());
});
marker.addTo(map);
map.setView(latLng, mapOptions.dataZoom);
setLatLng(latLng);
// only one single marker allowed
map.off('click', mapClickListen);
}

// create the map
$container.append($mapContainer);
map = L.map($mapContainer[0]).setView(mapOptions.initialCenter, mapOptions.initialZoom);
L.tileLayer(mapOptions.url, mapProviderOptions).addTo(map);
map.on('click', mapClickListen);

// add a search bar
L.Control.geocoder({
collapsed: false,
defaultMarkGeocode: false
}).on('markgeocode', function(e) {
setMarker(e.geocode.center);
}).addTo(map);

// set marker if model has data already
if ($latitudeField.val() && $longitudeField.val()) {
setMarker(getLatLng());
map.setView(getLatLng(), mapOptions.dataZoom, {animate: false});
}

// listen to keyboard input
$latitudeField.add($longitudeField).bind('keyup', function() {
setMarker(getLatLng());
});

// refresh map if inside jquery ui tabs widget and active tab changed
$container.parents('#tabs').on('tabsactivate', function() {
map.invalidateSize();
});
});
});
})(django.jQuery);
3 changes: 2 additions & 1 deletion geoposition/tests/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@

admin.autodiscover()

urlpatterns = patterns('',
urlpatterns = patterns(
'',
url(r'^$', 'example.views.poi_list'),
url(r'^admin/', include(admin.site.urls)),
)
28 changes: 23 additions & 5 deletions geoposition/widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,30 @@
class GeopositionWidget(forms.MultiWidget):
template_name = 'geoposition/widgets/geoposition.html'

backends = {
'google': {
'js': (
'//maps.google.com/maps/api/js?key=%s' % settings.GOOGLE_MAPS_API_KEY,
'geoposition/google.js',
),
'css': (),
},
'leaflet': {
'js': (
'//unpkg.com/[email protected]/dist/leaflet.js',
'//unpkg.com/[email protected]/dist/Control.Geocoder.js',
'geoposition/leaflet.js',
),
'css': (
'//unpkg.com/[email protected]/dist/leaflet.css',
'//unpkg.com/[email protected]/dist/Control.Geocoder.css',
),
}
}

def __init__(self, attrs=None):
self.Media.js = self.backends[settings.BACKEND]['js']
self.Media.css['all'] = self.backends[settings.BACKEND]['css'] + self.Media.css['all']
widgets = (
forms.TextInput(),
forms.TextInput(),
Expand All @@ -34,7 +57,6 @@ def get_config(self):
'marker_options': json.dumps(settings.MARKER_OPTIONS),
}


def get_context(self, name, value, attrs):
# Django 1.11 and up
context = super(GeopositionWidget, self).get_context(name, value, attrs)
Expand Down Expand Up @@ -64,10 +86,6 @@ def format_output(self, rendered_widgets):
})

class Media:
js = (
'//maps.google.com/maps/api/js?key=%s' % settings.GOOGLE_MAPS_API_KEY,
'geoposition/geoposition.js',
)
css = {
'all': ('geoposition/geoposition.css',)
}