I've got geodjango running using openlayers and OpenStreetMaps with the admin app.
Now I want to write some views to display the data. Basically, I just want to add a list of points (seen in the admin) to the map.
Geodjango appears to use a special openlayers.js file to do it's magic in the admin. Is there a good way to interface with this?
How can I write a view/template to display the geodjango data on a open street map window, as is seen in the admin?
At the moment, I'm digging into the openlayers.js file and api looking for an 'easy' solution. (I don't have js experience so this is taking some time.)
The current way I can see to do this is add the following as a template, and use django to add the code needed to display the points. (Based on the example here)
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Draw Feature Example</title>
<script src="http://www.openlayers.org/api/OpenLayers.js"></script>
<script type="text/javascript">
var map;
function init(){
map = new OpenLayers.Map('map');
var layer = new OpenLayers.Layer.WMS( "OpenLayers WMS",
"http://labs.metacarta.com/wms/vmap0", {layers: 'basic'} );
map.addLayer(layer);
/*
* Layer style
*/
// we want opaque external graphics and non-opaque internal graphics
var layer_style = OpenLayers.Util.extend({}, OpenLayers.Feature.Vector.style['default']);
layer_style.fillOpacity = 0.2;
layer_style.graphicOpacity = 1;
/*
* Blue style
*/
var style_blue = OpenLayers.Util.extend({}, layer_style);
style_blue.strokeColor = "blue";
style_blue.fillColor = "blue";
style_blue.graphicName = "star";
style_blue.pointRadius = 10;
style_blue.strokeWidth = 3;
style_blue.rotation = 45;
style_blue.strokeLinecap = "butt";
var vectorLayer = new OpenLayers.Layer.Vector("Simple Geometry", {style: layer_style});
// create a point feature
var point = new OpenLayers.Geometry.Point(-111.04, 45.68);
var pointFeature = new OpenLayers.Feature.Vector(point,null,style_blue);
// Add additional points/features here via django
map.addLayer(vectorLayer);
map.setCenter(new OpenLayers.LonLat(point.x, point.y), 5);
vectorLayer.addFeatures([pointFeature]);
}
</script>
</head>
<body onload="init()">
<div id="map" class="smallmap"></div>
</body>
</html>
Is this how it's done, or is there a better way?
Another solution is to create a form that utilizes the GeoDjango Admin widget.
To do this, I:
Setup a GeneratePolygonAdminClass:
class GeneratePolygonAdmin(admin.GeoModelAdmin):
list_filter=('polygon',)
list_display=('object', 'polygon')
Where the form is built:
geoAdmin=GeneratePolygonAdmin(ModelWithPolygonField, admin.site)
PolygonFormField=GeneratePolygon._meta.get_field('Polygon')
PolygonWidget=geoAdmin.get_map_widget(PolygonFormField)
Dict['Polygon']=forms.CharField(widget=PolygonWidget()) #In this case, I am creating a Dict to use for a dynamic form
Populating the widget of the form:
def SetupPolygonWidget(form, LayerName, MapFileName, DefaultPolygon=''):
form.setData({'Polygon':DefaultPolygon})
form.fields['Polygon'].widget.params['wms_layer']=LayerName
form.fields['Polygon'].widget.params['wms_url']='/cgi-bin/mapserv?MAP=' + MapFileName
form.fields['Polygon'].widget.params['default_lon']=-80.9
form.fields['Polygon'].widget.params['default_lat']=33.7
form.fields['Polygon'].widget.params['default_zoom']=11
form.fields['Polygon'].widget.params['wms_name']=YOURWMSLayerName
form.fields['Polygon'].widget.params['map_width']=800
form.fields['Polygon'].widget.params['map_height']=600
form.fields['Polygon'].widget.params['map_srid']=YOUR_SRID
form.fields['Polygon'].widget.params['modifiable']=True
form.fields['Polygon'].widget.params['map_options']={}
form.fields['Polygon'].widget.params['map_options']['buffer'] = 0
return form
Based on the code at:
http://code.djangoproject.com/browser/django/branches/gis/django/contrib/gis/admin/options.py?rev=7980
It looks like you can use the extra_js option to include OpenStreetMap (I have not tested this).
This is quite old, and I wouldn't go around creating a template hack as I originally was thinking. Now I would use leaflet.js with an ajax request to a django view that returns geojson to a leaflet geojson layer.
This makes the django side super easy.
Sample Django View:
# -*- coding: utf-8 -*-
'''
'''
import json
from django.http import HttpResponse, HttpResponseBadRequest
from django.contrib.gis.geos import Polygon
from models import ResultLayer, MyModel
def get_layer_polygons(request, layer_id):
"""
Return the polygons for the given bbox (bounding box)
"""
layer = ResultLayer.objects.get(id=layer_id)
bbox_raw = request.GET.get("bbox", None)
# Make sure the incoming bounding box is correctly formed!
bbox = None
if bbox_raw and bbox_raw.count(",") == 3:
bbox = [float(v) for v in bbox_raw.split(",")]
if not bbox:
msg = "Improperly formed or not given 'bbox' querystring option, should be in the format '?bbox=minlon,minlat,maxlon,maxlat'"
return HttpResponseBadRequest(msg)
bbox_poly = Polygon.from_bbox(bbox)
bbox_poly.srid = 900913 # google
bbox_poly.transform(layer.srid) # transform to the layer's srid for querying
bin_size = int(bin_size)
# build vector polygons from bin
results = MyModel.objects.filter(layer=layer, poly__intersects=bbox_poly).transform(900913, field_name="poly")
geojson_data = []
for r in results:
# loading json in order to dump json list later
gjson = r.poly.geojson
py_gjson = json.loads(gjson)
geojson_data.append(py_gjson)
return HttpResponse(json.dumps(geojson_data), mimetype='application/json')
I think your solution is workable and probably the easiest approach. Just templatize the javascript and use Django to inject your data points as the template is rendered.
If you wanted to get fancier, you could have a Django view that served up the data points as JSON (application/json) and then use AJAX to call back and retrieve the data based on events that are happening in the browser. If you want your application to be highly interactive above and beyond what OpenLayers provides, this might be worth the added complexity, but of course it all depends on the needs of your application.
You could consider using FloppyForms. In the end, I usually end up customizing the solution to my own needs, but it's a nice way to get started.
Checkout this tutorial from the geodjango-basic-apps project:
http://code.google.com/p/geodjango-basic-apps/wiki/FOSS4GWorkshop
maybe you don't have to hack up your own javascript just yet
Related
I'm creating a web page (in Python) of hiking trails in MA. I want to be able to click on the name of a trail, and have my web page display a google map of the location. In my database, I have 2 columns, for latitude and longitude. I'm pretty new to Python, and don't really know how to go about this. Here is the code that I've written:
def getMap():
"""
This is a middleware function which returns
latitude and longitude coordinates
from our database.
"""
# connect to db
conn, cursor = getConnectionAndCursor()
# prepare SQL
sql = """
SELECT lat
AND lng
FROM hiking
WHERE name = %s
"""
parameters=(name,)
# run the SQL
cursor.execute(sql, name)
# fetch the results
data = cursor.fetchall()
# clean up
cursor.close()
conn.close()
return data
def showMap(lat,lng):
"""
Presentation layer function to display a Google map.
"""
## create an HTML table for output:
print"""
<script
src="http://maps.googleapis.com/maps/api/js">
</script>
<script>
function initialize() {
var mapProp = {
center:new google.maps.LatLng(%s,%s),
zoom:15,
mapTypeId:google.maps.MapTypeId.ROADMAP
};
var map=new google.maps.Map(document.getElementById("googleMap"), mapProp);
}
google.maps.event.addDomListener(window, 'load', initialize);
</script>
</head>
<div id="googleMap" style="width:500px;height:380px;"></div>
""" % (lat, lng)
if __name__ == "__main__":
# get form field data
form = cgi.FieldStorage()
debugFormData(form)
doHTMLHead("MassHike: A database of popular Massachusetts trails")
if 'name' in form:
name=form['name'].value
mapdata = getMap()
showMap()
else:
data = getAllHikes()
showAllHikes(data)
doHTMLTail()
I asked a teaching assistant for help on this today, and she had no clue how to go about this. My web page is giving me an error saying ': showMap() takes exactly 2 arguments (0 given)', so I'm doing something very wrong. Any suggestions would be appreciated!
You need to include the arguments when you call the function. Currently you are just calling showMap(). Your function def showMap(lat,lng) declares two required arguments; lat & lng. You must provide theses when calling it.
I do not see where you are declaring the latitude and longitude. You need to get these numbers somewhere and put them into the function.
Example call w/ literals:
showMap('123','999')
Example call w/ vars:
varLat = 40.44062479999999
varLng = -79.99588640000002
showMap(varLat,varLng)
I am trying to write an Ember CLI application that talks to a REST api developed using Django-Rest-Framework.
I tried to ember-django-adapter as my data adpater for the ember application, however I cannot find a sample code on how to configure and write a model to use this data adapter. Can someone please help.
This is the EDA code https://github.com/dustinfarris/ember-django-adapter.
Also all I did on the ember app side is to create new app, and change the config as recommended here http://dustinfarris.com/ember-django-adapter/configuring/:
if (environment === 'development') {
ENV.APP.API_HOST = 'http://localhost:8000';
}
if (environment === 'production') {
ENV.APP.API_HOST = 'https://api.myproject.com';
ENV.APP.API_NAMESPACE = 'v2';
}
but this doc, doesn't say how to configure the data adapter for ember! Please let me know if there is a way to make ember js and django-rest-framework talk.
Thanks.
Before using Ember-data, I would suggest you to create a basic Ajax call using jQuery.
Step 1 (basic AJAX call with jQuery):
route.js:
model() {
return Ember.$.getJSON("/api/v1/foo");
}
Step 2 (Create the model foo with the correct adapter using ActiveModelAdapter):
models/foo.js:
import DS from 'ember-data';
var attr = DS.attr;
export default DS.Model.extend({
bar: attr('string'),
isTest: attr('boolean')
});
adapters/foo.js:
import DS from 'ember-data';
import config from 'shippo-frontend/config/environment';
import Ember from 'ember';
export default DS.ActiveModelAdapter.extend({
namespace: 'api/v1',
host: apiUrl
});
Step 3 (replace your jQuery call by the Ember-data call):
route.js:
model() {
return this.get('store').findAll('foo');
}
Notes:
active-model-adapter allows you to transform your snake_keys into camelCaseKeys. https://github.com/ember-data/active-model-adapter
If you need to do other modifications on the data coming from Django, create a serializers/foo.js and play around with the payload.
I've been reading the book 'Head First Python' where the writer talks about creating dynamic webpages using a module he created called 'yate', an HTML template engine (which I renamed to site_yate in the code below). The example he works through is a hypothetical coach wanting his athletes to be able to check their times online. The design is as follows: first you enter the homepage which has a link to run a script which generates a webpage where you can select the athlete whose times you want to view. Then when you select your athlete and click submit the form calls another script called "site_generate_timing_data.py" where you can views the athlete's top times. So I decided to take it further and add functionality to add a time for the athlete, using this extra line of code in my python script.
print(site_yate.do_form("addtime.py", [athlete_id]))
The HTML this will generate will be this:
<form action="addtime.py" method="POST">
<h1>Want to add a time?</h1>
<input type="Text" name="1" size=40> //"1" is the athlete's id in this example
<input type="Submit" value="Submit">
</form>
As you can see this code calls the script 'addtime.py' which has the following code:
import cgi
import sqlite3
data = cgi.FieldStorage().value[0] #this attribute will be in the form MininFieldStorage(name, value)
id = data.name #this attribute is the input's name i.e. athlete's id
time = data.value #this attribute is the input's value i.e. the time
connection = sqlite3.connect("NUACDB.sqlite") #my DB's name
cursor = connection.cursor()
cursor.execute("""INSERT INTO timing_data (athlete_id, time)
VALUES (?, ?)""",
(id, time)) #just SQL stuff
connection.commit()
connection.close()
Which works fine, however I want to change a few thing about this, since it leaves the user on a blank page. I could generate some HTML code to provide links to the homepage etc. or even JavaScript code to redirect the user automatically, but I want to keep this script HTML-free so that I can also use it elsewhere.
What I want to do instead is make the script execute on the same page. Not only that, but I would also prefer if I could put the addtime.py code as a function in another module called 'athletemodel.py' and call it form there, i.e. athletemodel.addtime() (or I could do from athletemodel import addtime so I can call the function directly). How can I call a python function using HTML code? I'm aware of the onsubmit="" form attribute but apparently that is for JavaScript functions. Another thing I'm unsure about is whether the data submitted in the form will still be accessible through CGI FieldStorage and hence whether my addtime.py code will still work as it is.
This stuff is so confusing! All help is appreciated.
Not sure if you already had it in mind, but I would use ajax (remember to include the jQuery library). Here's a rough example to get you started if this is what you want. It'll keep them on the same page:
JavaScript file:
$('#submitButtonId').click(function (event) {
event.preventDefault();
$('#submitButtonId').hide();
$('#thinking').show(); //some div with a nice ajax loader gif...
$.ajax({
type: 'POST',
data: $('#formId').serialize(),
url: '/URL_path_to_function',
success: function (data) {
$('#loading').hide();
var response = data.result //now do stuff with your response
}
error: function(error){
console.log('Error')}
});
Python view/function:
import jsonify
if request.method == 'POST':
value = request.form['input value'] #flask...
#Do stuff
return jsonify(result='Some response')
I'm trying to use create_logout_url to create a sort of admin menu in a trivial appengine-site, but it is currently behaving quite wierdly. Take a look at the code below:
menu = []
logout_link = "<a href='%s'>Log out</a>" % users.create_logout_url('/blog')
menu.append(logout_link)
new_entry = ''
if users.is_current_user_admin():
new_entry = "<a href='%(newpost)s'>New entry</a>" % {'newpost': self.uri_for('blog_entry')}
menu.append(new_entry)
return ','.join(menu)
The expected output should be something like:
<a href='/_ah/login?continue=http%3A//localhost%3A8080/blog&action=Logout'>Log out</a>,<a href='/blog/newpost'>New entry</a>
But it's actually:
Log out
Any ideas?
Update
I'm trying to use the above code in my base handler (which contains a lot of code that gets reused everywhere, like this admin menu and templating functions), if it helps or matters.
Changing the logout_link-part to:
logout_link = users.create_logout_url('/blog')
Results in the following output:
Log out
(I'd already tried that before, however, but to no avail)
Monotux, could you provide us with some more details about your code. You said you are using some other menus and templating functions.
The snippet provided generates the correct code:
<a href='/_ah/login?continue=http%3A//localhost%3A8080/blog&action=Logout'>Log out</a>,<a href='/blog/newpost'>New entry</a>
And given the two outputs you are seeing:
Log out
Log out
wich both are of the form
<a href='...'>Log out</a>
I think you are embedding the response of the snippet in some other code.
So, I'm trying to make a simple call using jQuery .getJSON to my local web server using python/django to serve up its requests. The address being used is:
http://localhost:8000/api/0.1/tonight-mobile.json?callback=jsonp1290277462296
I'm trying to write a simple web view that can access this url and return a JSON packet as the result (worried about actual element values/layout later).
Here's my simple attempt at just alerting/returning the data:
$.getJSON("http://localhost:8000/api/0.1/tonight-mobile.json&callback=?",
function(json){
alert(json);
<!--$.each(json.items, function(i,item){
});-->
});
I am able to access this URL directly, either at http://localhost:8000/api/0.1/tonight-mobile.json or http://localhost:8000/api/0.1/tonight-mobile.json&callback=jsonp1290277462296 and get back a valid JSON packet... So I'm assuming it's in my noob javascript:)
My views.py function that is generating this response looks as follows:
def tonight_mobile(request):
callback = request.GET.get('callback=?', '')
def with_rank(rank, place):
return (rank > 0)
place_data = dict(
Places = [make_mobile_place_dict(request, p) for p in Place.objects.all()]
)
xml_bytes = json.dumps(place_data)
xml_bytes = callback + '(' + xml_bytes + ');'
return HttpResponse(xml_bytes, mimetype="application/json")
With corresponding urls.py configuration:
(r'^tonight-mobile.json','iphone_api.views.tonight_mobile'),
I am still somewhat confused on how to use callbacks, so maybe that is where my issue lies. Note I am able to call directly a 'blah.json' file that is giving me a response, but not through a wired URL. Could someone assist me with some direction?
First, callback = request.GET.get('callback=?', '') won't get you the value of callback.
callback = request.GET.get( 'callback', None )
Works much better.
To debug this kind of thing. You might want to include print statements in your Django view function so you can see what's going on. For example: print repr(request.GET) is a helpful thing to put in a view function so that you can see the GET dictionary.