Skip to content

Instantly share code, notes, and snippets.

@kwilcox
Last active June 20, 2019 13:42
Show Gist options
  • Save kwilcox/9419045 to your computer and use it in GitHub Desktop.
Save kwilcox/9419045 to your computer and use it in GitHub Desktop.
WTForms and GeoAlchemy2
from flask.ext.admin.contrib.sqla import ModelView
# 'location' is the GeoAlchemy2 column
class FeatureView(ModelView):
...
form_overrides = dict(location=WTFormsMapField)
form_args = dict(
location=dict(
geometry_type='Polygon', height=500, width=500, cloudmade_api="{your_api}"
)
)
...
import json
from wtforms import Field
import geojson
from shapely.geometry import asShape
from geoalchemy2.shape import to_shape, from_shape
from wtforms.widgets import html_params, HTMLString
from geoalchemy2.elements import WKTElement, WKBElement
class WTFormsMapInput(object):
def __call__(self, field, **kwargs):
options = dict(name=field.name, value=field.data, height=field.height, width=field.width, api=field.api)
html = self.load() + self.map(field.data, field.geometry_type) % options
return HTMLString(html)
def load(self):
return """
<link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.7.2/leaflet.css" />
<link rel="stylesheet" href="http://leaflet.github.io/Leaflet.draw/leaflet.draw.css" />
<script src="http://cdn.leafletjs.com/leaflet-0.7.2/leaflet.js"></script>
<script src="http://leaflet.github.io/Leaflet.draw/leaflet.draw.js"></script>
<script src="/admin/static/vendor/jquery-1.8.3.min.js" type="text/javascript"></script>
"""
def map(self, value, geometry_type):
""" Quick and dirty, don't hate."""
html = """
<div id="map" style="height: %(height)spx; width: %(width)spx;"></div>
<input id="geojson" type="text" name="%(name)s"></input>
<script>
var map = L.map('map').setView([0.0, 0.0], 5);
L.tileLayer('http://{s}.tile.cloudmade.com/%(api)s/997/256/{z}/{x}/{y}.png', {
attribution: 'Imagery from <a href="http://cloudmade.com">CloudMade</a>',
maxZoom: 18
}).addTo(map)
var editableLayers = L.geoJson().addTo(map);
"""
if value is not None:
subme = """var geojson = JSON.parse('%s');
editableLayers.addData(geojson);
update()
map.fitBounds(editableLayers.getBounds());"""
# If validation in Flask-Admin fails on somethign other than
# the spatial column, it is never converted to geojson. Didn't
# spend the time to figure out why, so I just convert here.
if isinstance(value, (WKTElement, WKBElement)):
html += subme % geojson.dumps(to_shape(value))
else:
html += subme % geojson.dumps(value)
html += """
var drawControl = new L.Control.Draw({
position: 'topright',
draw: {
polyline: false,
circle: false,
rectangle: false,
polygon: false,
marker: false,
"""
if unicode(geometry_type.lower()) == u'polygon':
html += "polygon: true"
elif unicode(geometry_type.lower()) == u'point':
html += "marker: true"
elif unicode(geometry_type.lower()) == u'linestring':
html += "polyline: true"
html += """
},
edit: {
featureGroup: editableLayers
},
});
map.addControl(drawControl);
map.on('draw:created', function (e) {
editableLayers.addLayer(e.layer);
update();
});
map.on('draw:edited', function (e) {
// Just use the first layer
update();
})
map.on('draw:deleted', function (e) {
update();
})
function update() {
if (editableLayers.getLayers().length > 0) {
$("#geojson").val(JSON.stringify(editableLayers.getLayers()[0].toGeoJSON()));
} else {
$("#geojson").val(null);
}
}
</script>
"""
return html
class WTFormsMapField(Field):
widget = WTFormsMapInput()
def __init__(self, label='', validators=None, geometry_type=None, width=500, height=500, cloudmade_api="", **kwargs):
super(WTFormsMapField, self).__init__(label, validators, **kwargs)
self.width = width
self.height = height
self.api = cloudmade_api
self.geometry_type = geometry_type
def _value(self):
""" Called by widget to get GeoJSON representation of object """
if self.data:
return self.data
else:
return json.loads(json.dumps(dict()))
def process_formdata(self, valuelist):
""" Convert GeoJSON to DB object """
if valuelist:
geo_ob = geojson.loads(valuelist[0])
# Convert the Feature into a Shapely geometry and then to GeoAlchemy2 object
# We could do something with the properties of the Feature here...
self.data = from_shape(asShape(geo_ob.geometry))
else:
self.data = None
def process_data(self, value):
""" Convert DB object to GeoJSON """
if value is not None:
self.data = geojson.loads(geojson.dumps(to_shape(value)))
else:
self.data = None
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment