Last active
June 20, 2019 13:42
-
-
Save kwilcox/9419045 to your computer and use it in GitHub Desktop.
WTForms and GeoAlchemy2
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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}" | |
) | |
) | |
... |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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